diff --git a/wfe-core/src/main/java/ru/runa/wfe/script/common/IdentitiesSetContainer.java b/wfe-core/src/main/java/ru/runa/wfe/script/common/IdentitiesSetContainer.java index e2fe9581ad..5016fc447c 100644 --- a/wfe-core/src/main/java/ru/runa/wfe/script/common/IdentitiesSetContainer.java +++ b/wfe-core/src/main/java/ru/runa/wfe/script/common/IdentitiesSetContainer.java @@ -34,7 +34,7 @@ protected IdentitiesSetContainer(NamedIdentityType identitiesType) { protected void validate(ScriptOperation scriptOperation, boolean requiredNotEmpty) { for (NamedIdentitySet set : identitySets) { - set.ensureType(scriptOperation, identitiesType); + set.validate(scriptOperation, identitiesType); } for (Identity identity : identities) { identity.validate(scriptOperation); diff --git a/wfe-core/src/main/java/ru/runa/wfe/script/common/IdentitiesSetContainerOperation.java b/wfe-core/src/main/java/ru/runa/wfe/script/common/IdentitiesSetContainerOperation.java index f925c80bac..9b454bc295 100644 --- a/wfe-core/src/main/java/ru/runa/wfe/script/common/IdentitiesSetContainerOperation.java +++ b/wfe-core/src/main/java/ru/runa/wfe/script/common/IdentitiesSetContainerOperation.java @@ -31,7 +31,7 @@ protected IdentitiesSetContainerOperation(NamedIdentityType identitiesType) { protected void validate(boolean requiredNotEmpty) { for (NamedIdentitySet set : identitySets) { - set.ensureType(this, identitiesType); + set.validate(this, identitiesType); } if (requiredNotEmpty) { if (identitySets.size() == 0) { diff --git a/wfe-core/src/main/java/ru/runa/wfe/script/common/NamedIdentitySet.java b/wfe-core/src/main/java/ru/runa/wfe/script/common/NamedIdentitySet.java index a16f3ebadd..94e3714b91 100644 --- a/wfe-core/src/main/java/ru/runa/wfe/script/common/NamedIdentitySet.java +++ b/wfe-core/src/main/java/ru/runa/wfe/script/common/NamedIdentitySet.java @@ -20,7 +20,7 @@ public class NamedIdentitySet { @XmlAttribute(name = AdminScriptConstants.NAME_ATTRIBUTE_NAME) public String name; - @XmlAttribute(name = AdminScriptConstants.TYPE_ATTRIBUTE_NAME, required = true) + @XmlAttribute(name = AdminScriptConstants.TYPE_ATTRIBUTE_NAME) public NamedIdentityType type; @XmlElement(name = AdminScriptConstants.IDENTITY_ELEMENT_NAME, namespace = AdminScriptConstants.NAMESPACE) @@ -37,7 +37,10 @@ public class NamedIdentitySet { * @param requiredType * Required identity set type */ - public void ensureType(ScriptOperation scriptOperation, NamedIdentityType requiredType) { + public void validate(ScriptOperation scriptOperation, NamedIdentityType requiredType) { + if (type == null) { + throw new ScriptValidationException(scriptOperation, "Type is required."); + } if (requiredType != type) { throw new ScriptValidationException(scriptOperation, "Required identity set of type " + requiredType.getScriptName() + "."); } @@ -45,7 +48,7 @@ public void ensureType(ScriptOperation scriptOperation, NamedIdentityType requir if (innerSet.type != type) { throw new ScriptValidationException(scriptOperation, "Inner named identity must be the same type as outer."); } - innerSet.ensureType(scriptOperation, requiredType); + innerSet.validate(scriptOperation, requiredType); } } @@ -92,21 +95,21 @@ public void register(ScriptExecutionContext context) { @XmlEnum public enum NamedIdentityType { - @XmlEnumValue(value = "DEFINITION") - PROCESS_DEFINITION(SecuredObjectType.DEFINITION), + @XmlEnumValue(value = "ProcessDefinition") + PROCESS_DEFINITION(SecuredObjectType.DEFINITION, "ProcessDefinition"), - @XmlEnumValue(value = "EXECUTOR") - EXECUTOR(SecuredObjectType.EXECUTOR), + @XmlEnumValue(value = "Executor") + EXECUTOR(SecuredObjectType.EXECUTOR, "Executor"), - @XmlEnumValue(value = "REPORT") - REPORT(SecuredObjectType.REPORT); + @XmlEnumValue(value = "Report") + REPORT(SecuredObjectType.REPORT, "Report"); - private SecuredObjectType securedObjectType; - private String scriptName; + private final SecuredObjectType securedObjectType; + private final String scriptName; - NamedIdentityType(SecuredObjectType type) { + NamedIdentityType(SecuredObjectType type, String scriptName) { this.securedObjectType = type; - this.scriptName = type.getName(); + this.scriptName = scriptName; } public String getScriptName() { diff --git a/wfe-core/src/main/java/ru/runa/wfe/script/common/ScriptExecutionContext.java b/wfe-core/src/main/java/ru/runa/wfe/script/common/ScriptExecutionContext.java index af7f3639bc..4cd3472a8e 100644 --- a/wfe-core/src/main/java/ru/runa/wfe/script/common/ScriptExecutionContext.java +++ b/wfe-core/src/main/java/ru/runa/wfe/script/common/ScriptExecutionContext.java @@ -1,13 +1,10 @@ package ru.runa.wfe.script.common; +import com.google.common.collect.Maps; import java.util.HashMap; import java.util.Map; import java.util.Set; - import org.springframework.beans.factory.annotation.Autowired; - -import com.google.common.collect.Maps; - import ru.runa.wfe.bot.logic.BotLogic; import ru.runa.wfe.commons.ApplicationContextFactory; import ru.runa.wfe.definition.logic.DefinitionLogic; @@ -25,7 +22,7 @@ public class ScriptExecutionContext { @Autowired - ExecutorLogic executorLogic; + private ExecutorLogic executorLogic; @Autowired private RelationLogic relationLogic; @Autowired diff --git a/wfe-core/src/main/java/ru/runa/wfe/script/executor/SetActorInactiveOperation.java b/wfe-core/src/main/java/ru/runa/wfe/script/executor/SetActorInactiveOperation.java index 1906169378..510aab7c2a 100644 --- a/wfe-core/src/main/java/ru/runa/wfe/script/executor/SetActorInactiveOperation.java +++ b/wfe-core/src/main/java/ru/runa/wfe/script/executor/SetActorInactiveOperation.java @@ -34,7 +34,7 @@ public class SetActorInactiveOperation extends ScriptOperation { public void validate(ScriptExecutionContext context) { ScriptValidation.requiredAttribute(this, AdminScriptConstants.NAME_ATTRIBUTE_NAME, name); for (NamedIdentitySet set : identities) { - set.ensureType(this, NamedIdentityType.EXECUTOR); + set.validate(this, NamedIdentityType.EXECUTOR); } } diff --git a/wfe-core/src/main/java/ru/runa/wfe/script/permission/ChangePermissionsOperation.java b/wfe-core/src/main/java/ru/runa/wfe/script/permission/ChangePermissionsOperation.java index 35a40111d0..361350671e 100644 --- a/wfe-core/src/main/java/ru/runa/wfe/script/permission/ChangePermissionsOperation.java +++ b/wfe-core/src/main/java/ru/runa/wfe/script/permission/ChangePermissionsOperation.java @@ -26,19 +26,15 @@ public abstract class ChangePermissionsOperation extends ScriptOperation { @XmlAttribute(name = "executor", required = true) protected String xmlExecutor; - @SuppressWarnings("unused") @XmlAttribute(name = "type") private String xmlType; - @SuppressWarnings("unused") @XmlAttribute(name = "name") private String xmlName; - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @XmlElement(name = "namedIdentitySet", namespace = AdminScriptConstants.NAMESPACE) private List xmlNamedIdentitySets = Lists.newArrayList(); - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") @XmlElement(name = "permission", namespace = AdminScriptConstants.NAMESPACE) private List xmlPermissions = new ArrayList<>(); diff --git a/wfe-core/src/main/java/ru/runa/wfe/security/logic/LdapLogic.java b/wfe-core/src/main/java/ru/runa/wfe/security/logic/LdapLogic.java index 6c6461dabf..eaf99c6da2 100644 --- a/wfe-core/src/main/java/ru/runa/wfe/security/logic/LdapLogic.java +++ b/wfe-core/src/main/java/ru/runa/wfe/security/logic/LdapLogic.java @@ -33,10 +33,12 @@ import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; -import javax.naming.SizeLimitExceededException; import javax.naming.directory.Attribute; -import javax.naming.directory.DirContext; -import javax.naming.directory.InitialDirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.InitialLdapContext; +import javax.naming.ldap.LdapContext; +import javax.naming.ldap.PagedResultsControl; +import javax.naming.ldap.PagedResultsResponseControl; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import org.apache.commons.logging.Log; @@ -70,12 +72,7 @@ public class LdapLogic { private static final String ATTR_ACCOUNT_NAME = LdapProperties.getSynchronizationAccountNameAttribute(); private static final String ATTR_GROUP_NAME = LdapProperties.getSynchronizationGroupNameAttribute(); private static final String ATTR_GROUP_MEMBER = LdapProperties.getSynchronizationGroupMemberAttribute(); - // for paging - private static final String LOGIN_FIRST_LETTER_FILTER = "(&(|({0}={1}*)({0}={2}*)){3})"; - private static final String[] ALPHABETS = { "А", "Б", "В", "Г", "Д", "Е", "Ё", "Ж", "З", "И", "К", "Л", "М", "Н", "О", "П", "Р", "С", "Т", "У", - "Ф", "Х", "Ч", "Ц", "Ш", "Щ", "Э", "Ю", "Я", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", - "S", "T", "U", "V", "W", "X", "Y", "Z" }; - + private static int pageSize = LdapProperties.getLdapPageSize(); @Autowired protected ExecutorDao executorDao; @Autowired @@ -91,15 +88,15 @@ public synchronized int synchronizeExecutors() { } log.info("Synchronizing executors"); try { - importGroup = loadGroup(new Group(LdapProperties.getSynchronizationImportGroupName(), - LdapProperties.getSynchronizationImportGroupDescription())); - wasteGroup = loadGroup(new Group(LdapProperties.getSynchronizationWasteGroupName(), - LdapProperties.getSynchronizationWasteGroupDescription())); - DirContext dirContext = getContext(); + importGroup = loadGroup( + new Group(LdapProperties.getSynchronizationImportGroupName(), LdapProperties.getSynchronizationImportGroupDescription())); + wasteGroup = loadGroup( + new Group(LdapProperties.getSynchronizationWasteGroupName(), LdapProperties.getSynchronizationWasteGroupDescription())); + LdapContext ldapContext = getContext(); Map actorsByDistinguishedName = Maps.newHashMap(); - int changesCount = synchronizeActors(dirContext, actorsByDistinguishedName); - changesCount += synchronizeGroups(dirContext, actorsByDistinguishedName); - dirContext.close(); + int changesCount = synchronizeActors(ldapContext, actorsByDistinguishedName); + changesCount += synchronizeGroups(ldapContext, actorsByDistinguishedName); + ldapContext.close(); return changesCount; } catch (Exception e) { log.error("", e); @@ -108,7 +105,7 @@ public synchronized int synchronizeExecutors() { } } - private int synchronizeActors(DirContext dirContext, Map actorsByDistinguishedName) throws Exception { + private int synchronizeActors(LdapContext ldapContext, Map actorsByDistinguishedName) throws Exception { int changesCount = 0; List existingActorsList = executorDao.getAllActors(BatchPresentationFactory.ACTORS.createNonPaged()); Map existingActorsMap = Maps.newHashMap(); @@ -119,30 +116,32 @@ private int synchronizeActors(DirContext dirContext, Map actorsBy if (LdapProperties.isSynchronizationDeleteExecutors()) { ldapActorsToDelete.addAll(executorDao.getGroupActors(importGroup)); } - SearchControls controls = new SearchControls(); - controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); for (String ou : LdapProperties.getSynchronizationOrganizationUnits()) { List resultList = Lists.newArrayList(); - try { - NamingEnumeration list = dirContext.search(ou, OBJECT_CLASS_USER_FILTER, controls); - while (list.hasMore()) { - SearchResult searchResult = list.next(); - resultList.add(searchResult); + byte[] cookie = null; + ldapContext.setRequestControls(new Control[] { new PagedResultsControl(pageSize, Control.NONCRITICAL) }); + do { + NamingEnumeration results = ldapContext.search(ou, OBJECT_CLASS_USER_FILTER, searchControls); + while (results.hasMoreElements()) { + SearchResult result = results.nextElement(); + resultList.add(result); } - list.close(); - } catch (SizeLimitExceededException e) { - resultList.clear(); - for (String y : ALPHABETS) { - NamingEnumeration list = dirContext.search(ou, - MessageFormat.format(LOGIN_FIRST_LETTER_FILTER, ATTR_ACCOUNT_NAME, y, y.toLowerCase(), OBJECT_CLASS_USER_FILTER), - controls); - while (list.hasMore()) { - SearchResult searchResult = list.next(); - resultList.add(searchResult); + results.close(); + Control[] controls = ldapContext.getResponseControls(); + if (controls != null) { + for (Control control : controls) { + if (control instanceof PagedResultsResponseControl) { + PagedResultsResponseControl response = (PagedResultsResponseControl) control; + cookie = response.getCookie(); + } } - list.close(); + } else { + log.warn("Ldap server did not send controls for paging"); } - } + ldapContext.setRequestControls(new Control[] { new PagedResultsControl(pageSize, cookie, Control.CRITICAL) }); + } while (cookie != null); for (SearchResult searchResult : resultList) { String name = getStringAttribute(searchResult, ATTR_ACCOUNT_NAME); String description = getStringAttribute(searchResult, LdapProperties.getSynchronizationUserDescriptionAttribute()); @@ -232,7 +231,7 @@ private int synchronizeActors(DirContext dirContext, Map actorsBy return changesCount; } - private int synchronizeGroups(DirContext dirContext, Map actorsByDistinguishedName) throws NamingException { + private int synchronizeGroups(LdapContext ldapContext, Map actorsByDistinguishedName) throws Exception { int changesCount = 0; List existingGroupsList = executorDao.getAllGroups(); Map existingGroupsByLdapNameMap = Maps.newHashMap(); @@ -250,35 +249,35 @@ private int synchronizeGroups(DirContext dirContext, Map actorsBy } } } - SearchControls controls = new SearchControls(); - controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); Map groupResultsByDistinguishedName = Maps.newHashMap(); for (String ou : LdapProperties.getSynchronizationOrganizationUnits()) { - try { - NamingEnumeration list = dirContext.search(ou, OBJECT_CLASS_GROUP_FILTER, controls); - while (list.hasMore()) { - SearchResult searchResult = list.next(); + byte[] cookie = null; + ldapContext.setRequestControls(new Control[] { new PagedResultsControl(pageSize, Control.NONCRITICAL) }); + do { + NamingEnumeration results = ldapContext.search(ou, OBJECT_CLASS_GROUP_FILTER, searchControls); + while (results.hasMoreElements()) { + SearchResult searchResult = results.nextElement(); if (searchResult.getAttributes().get(ATTR_GROUP_MEMBER) == null) { continue; } groupResultsByDistinguishedName.put(searchResult.getNameInNamespace(), searchResult); } - list.close(); - } catch (SizeLimitExceededException e) { - for (String y : ALPHABETS) { - NamingEnumeration list = dirContext.search(ou, - MessageFormat.format(LOGIN_FIRST_LETTER_FILTER, ATTR_GROUP_NAME, y, y.toLowerCase(), OBJECT_CLASS_GROUP_FILTER), - controls); - while (list.hasMore()) { - SearchResult searchResult = list.next(); - if (searchResult.getAttributes().get(ATTR_GROUP_MEMBER) == null) { - continue; + results.close(); + Control[] controls = ldapContext.getResponseControls(); + if (controls != null) { + for (Control control : controls) { + if (control instanceof PagedResultsResponseControl) { + PagedResultsResponseControl response = (PagedResultsResponseControl) control; + cookie = response.getCookie(); } - groupResultsByDistinguishedName.put(searchResult.getNameInNamespace(), searchResult); } - list.close(); + } else { + log.warn("Ldap server did not send controls for paging"); } - } + ldapContext.setRequestControls(new Control[] { new PagedResultsControl(pageSize, cookie, Control.CRITICAL) }); + } while (cookie != null); } for (SearchResult searchResult : groupResultsByDistinguishedName.values()) { String name = getStringAttribute(searchResult, ATTR_GROUP_NAME); @@ -323,7 +322,7 @@ private int synchronizeGroups(DirContext dirContext, Map actorsBy Set actorsToDelete = Sets.newHashSet(executorDao.getGroupActors(group)); Set actorsToAdd = Sets.newHashSet(); Set groupTargetActors = Sets.newHashSet(); - fillTargetActorsRecursively(dirContext, groupTargetActors, searchResult, groupResultsByDistinguishedName, actorsByDistinguishedName); + fillTargetActorsRecursively(ldapContext, groupTargetActors, searchResult, groupResultsByDistinguishedName, actorsByDistinguishedName); for (Actor targetActor : groupTargetActors) { if (!actorsToDelete.remove(targetActor)) { actorsToAdd.add(targetActor); @@ -364,9 +363,9 @@ private Pattern getPatternForMissedPeople() { return patternForMissedPeople; } - private DirContext getContext() throws NamingException { + private LdapContext getContext() throws NamingException { Hashtable env = new Hashtable<>(LdapProperties.getAllProperties()); - return new InitialDirContext(env); + return new InitialLdapContext(env, null); } private Group loadGroup(Group group) { @@ -390,14 +389,14 @@ private String getStringAttribute(SearchResult searchResult, String name) throws return null; } - private void fillTargetActorsRecursively(DirContext dirContext, Set recursiveActors, SearchResult searchResult, + private void fillTargetActorsRecursively(LdapContext ldapContext, Set recursiveActors, SearchResult searchResult, Map groupResultsByDistinguishedName, Map actorsByDistinguishedName) throws NamingException { NamingEnumeration namingEnum = (NamingEnumeration) searchResult.getAttributes().get(ATTR_GROUP_MEMBER).getAll(); while (namingEnum.hasMore()) { String executorDistinguishedName = namingEnum.next(); SearchResult groupSearchResult = groupResultsByDistinguishedName.get(executorDistinguishedName); if (groupSearchResult != null) { - fillTargetActorsRecursively(dirContext, recursiveActors, groupSearchResult, groupResultsByDistinguishedName, + fillTargetActorsRecursively(ldapContext, recursiveActors, groupSearchResult, groupResultsByDistinguishedName, actorsByDistinguishedName); } else { Actor actor = actorsByDistinguishedName.get(executorDistinguishedName); @@ -406,7 +405,7 @@ private void fillTargetActorsRecursively(DirContext dirContext, Set recur } else { Matcher m = getPatternForMissedPeople().matcher(executorDistinguishedName); String executorPath = m.replaceAll(""); - Attribute samAttribute = dirContext.getAttributes(executorPath).get(ATTR_ACCOUNT_NAME); + Attribute samAttribute = ldapContext.getAttributes(executorPath).get(ATTR_ACCOUNT_NAME); if (samAttribute != null) { String executorName = samAttribute.get().toString(); log.debug("Executor name " + executorDistinguishedName + " fetched by invocation: " + executorName); diff --git a/wfe-core/src/main/java/ru/runa/wfe/security/logic/LdapProperties.java b/wfe-core/src/main/java/ru/runa/wfe/security/logic/LdapProperties.java index 2acf10de62..516fb5ab2b 100644 --- a/wfe-core/src/main/java/ru/runa/wfe/security/logic/LdapProperties.java +++ b/wfe-core/src/main/java/ru/runa/wfe/security/logic/LdapProperties.java @@ -47,6 +47,10 @@ public static boolean isSynchronizationUpdateExecutors() { public static boolean isSynchronizationDeleteExecutors() { return RESOURCES.getBooleanProperty("synchronization.delete.executors.enabled", false); } + + public static int getLdapPageSize() { + return RESOURCES.getIntegerProperty("synchronization.pageSize", 1000); + } public static List getSynchronizationOrganizationUnits() { return RESOURCES.getMultipleStringProperty("synchronization.organization.units"); diff --git a/wfe-core/src/main/resources/ldap.properties.sample b/wfe-core/src/main/resources/ldap.properties.sample index 0a20160ee2..b2052a645a 100644 --- a/wfe-core/src/main/resources/ldap.properties.sample +++ b/wfe-core/src/main/resources/ldap.properties.sample @@ -45,6 +45,8 @@ synchronization.create.executors.enabled = true synchronization.update.executors.enabled = true synchronization.delete.executors.enabled = true synchronization.user.status.enabled = true +# the number of entries to return in a page +synchronization.pageSize = 1000 # types synchronization.object.class.filter = (objectclass={0}) synchronization.user.object.class = user diff --git a/wfe-web/pom.xml b/wfe-web/pom.xml index d0da9ce5c6..a5560ce45f 100644 --- a/wfe-web/pom.xml +++ b/wfe-web/pom.xml @@ -153,6 +153,11 @@ 1.5 compile + + com.cloudbees + diff4j + 1.3 + diff --git a/wfe-web/src/main/java/ru/runa/common/web/action/ViewLogsAction.java b/wfe-web/src/main/java/ru/runa/common/web/action/ViewLogsAction.java index 06f45a4760..4247ad6f04 100644 --- a/wfe-web/src/main/java/ru/runa/common/web/action/ViewLogsAction.java +++ b/wfe-web/src/main/java/ru/runa/common/web/action/ViewLogsAction.java @@ -20,7 +20,6 @@ import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import ru.runa.common.WebResources; -import ru.runa.common.web.Commons; import ru.runa.common.web.HTMLUtils; import ru.runa.common.web.Resources; import ru.runa.common.web.form.ViewLogForm; @@ -28,7 +27,6 @@ import ru.runa.wfe.security.Permission; import ru.runa.wfe.security.SecuredSingleton; import ru.runa.wfe.service.delegate.Delegates; -import ru.runa.wfe.user.User; /** * @author dofs diff --git a/wfe-web/src/main/java/ru/runa/common/web/html/RadioButtonTdBuilder.java b/wfe-web/src/main/java/ru/runa/common/web/html/RadioButtonTdBuilder.java new file mode 100644 index 0000000000..830baddd74 --- /dev/null +++ b/wfe-web/src/main/java/ru/runa/common/web/html/RadioButtonTdBuilder.java @@ -0,0 +1,56 @@ +/* + * This file is part of the RUNA WFE project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 + * of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +package ru.runa.common.web.html; + +import org.apache.ecs.html.Input; +import org.apache.ecs.html.TD; +import ru.runa.common.web.Resources; + +public class RadioButtonTdBuilder extends BaseTdBuilder { + private final String inputName; + private final String propertyName; + + public RadioButtonTdBuilder(String inputName, String propertyName) { + super(null); + this.inputName = inputName; + this.propertyName = propertyName; + } + + @Override + public TD build(Object object, Env env) { + Input input = new Input(Input.RADIO, inputName, getValue(object, env)); + TD td = new TD(input); + td.setClass(Resources.CLASS_LIST_TABLE_TD); + return td; + } + + @Override + public String getValue(Object object, Env env) { + return readProperty(object, propertyName, true); + } + + @Override + public String[] getSeparatedValues(Object object, Env env) { + return new String[] { getValue(object, env) }; + } + + @Override + public int getSeparatedValuesCount(Object object, Env env) { + return 1; + } +} diff --git a/wfe-web/src/main/java/ru/runa/common/web/html/SortingHeaderBuilder.java b/wfe-web/src/main/java/ru/runa/common/web/html/SortingHeaderBuilder.java index 25bfd6a751..5de583b016 100644 --- a/wfe-web/src/main/java/ru/runa/common/web/html/SortingHeaderBuilder.java +++ b/wfe-web/src/main/java/ru/runa/common/web/html/SortingHeaderBuilder.java @@ -17,20 +17,16 @@ */ package ru.runa.common.web.html; +import com.google.common.collect.Maps; import java.util.HashMap; import java.util.Map; - import javax.servlet.jsp.PageContext; - import org.apache.ecs.Entities; import org.apache.ecs.html.A; import org.apache.ecs.html.IMG; import org.apache.ecs.html.TD; import org.apache.ecs.html.TH; import org.apache.ecs.html.TR; - -import com.google.common.collect.Maps; - import ru.runa.common.web.Commons; import ru.runa.common.web.HTMLUtils; import ru.runa.common.web.Messages; @@ -58,18 +54,31 @@ public class SortingHeaderBuilder implements HeaderBuilder { private final String[] prefixNames; private final String[] suffixNames; private final String returnActionName; + private final boolean addSelectAllToFirstPrefixCell; private static String[] createEmptyStrings(int size) { return (String[]) ArraysCommons.fillArray(new String[size], Entities.NBSP); } public SortingHeaderBuilder(BatchPresentation batchPresentation, String[] prefixNames, String[] suffixNames, String returnActionName, - PageContext pageContext) { + PageContext pageContext, boolean addSelectAllToFirstPrefixCell) { this.batchPresentation = batchPresentation; this.prefixNames = prefixNames; this.suffixNames = suffixNames; this.returnActionName = returnActionName; this.pageContext = pageContext; + this.addSelectAllToFirstPrefixCell = addSelectAllToFirstPrefixCell; + } + + public SortingHeaderBuilder(BatchPresentation batchPresentation, String[] prefixNames, String[] suffixNames, String returnActionName, + PageContext pageContext) { + this(batchPresentation, prefixNames, suffixNames, returnActionName, pageContext, true); + } + + public SortingHeaderBuilder(BatchPresentation batchPresentation, int numberOfPrefixCells, int numberOfSuffixCells, String returnActionName, + PageContext pageContext, boolean addSelectAllToFirstPrefixCell) { + this(batchPresentation, createEmptyStrings(numberOfPrefixCells), createEmptyStrings(numberOfSuffixCells), returnActionName, pageContext, + addSelectAllToFirstPrefixCell); } public SortingHeaderBuilder(BatchPresentation batchPresentation, int numberOfPrefixCells, int numberOfSuffixCells, String returnActionName, @@ -87,7 +96,7 @@ public TR build() { } TR tr = new TR(); createCells(tr, createEmptyStrings(getAditionalNumberOfPrefixEmptyCells())); - if (prefixNames.length > 0) { + if (addSelectAllToFirstPrefixCell && prefixNames.length > 0) { prefixNames[0] = Entities.PLUSMN; } createCells(tr, prefixNames); diff --git a/wfe-web/src/main/java/ru/runa/wf/web/MessagesProcesses.java b/wfe-web/src/main/java/ru/runa/wf/web/MessagesProcesses.java index f51226fd62..8545fc86ea 100644 --- a/wfe-web/src/main/java/ru/runa/wf/web/MessagesProcesses.java +++ b/wfe-web/src/main/java/ru/runa/wf/web/MessagesProcesses.java @@ -106,4 +106,7 @@ public final class MessagesProcesses { public static final StrutsMessage PAYLOAD_PARAMETER_NAME = new StrutsMessage("label.payload_parameter_name"); public static final StrutsMessage PAYLOAD_PARAMETER_VALUE = new StrutsMessage("label.payload_parameter_value"); public static final StrutsMessage SIGNAL_MESSAGE_IS_SENT = new StrutsMessage("signal.message_is_sent"); + public static final StrutsMessage BUTTON_VIEW_DIFFERENCES = new StrutsMessage("button.view_differences"); + public static final StrutsMessage FAILED_VIEW_DIFFERENCES = new StrutsMessage("failed.view_differences"); + public static final StrutsMessage LABEL_NO_DIFFERENCES_FOUND = new StrutsMessage("label.no_differences_found"); } diff --git a/wfe-web/src/main/java/ru/runa/wf/web/action/ShowDefinitionHistoryAction.java b/wfe-web/src/main/java/ru/runa/wf/web/action/ShowDefinitionHistoryAction.java index 9cdd07d0e7..a809204b95 100644 --- a/wfe-web/src/main/java/ru/runa/wf/web/action/ShowDefinitionHistoryAction.java +++ b/wfe-web/src/main/java/ru/runa/wf/web/action/ShowDefinitionHistoryAction.java @@ -2,15 +2,16 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; - import ru.runa.common.web.ProfileHttpSessionHelper; +import ru.runa.common.web.Resources; import ru.runa.common.web.action.ActionBase; import ru.runa.common.web.form.IdNameForm; +import ru.runa.wfe.definition.DefinitionHistoryClassPresentation; import ru.runa.wfe.presentation.BatchPresentation; +import ru.runa.wfe.presentation.BatchPresentationConsts; import ru.runa.wfe.presentation.filter.StringFilterCriteria; /** @@ -24,15 +25,17 @@ public class ShowDefinitionHistoryAction extends ActionBase { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { String processName = ((IdNameForm) form).getName(); BatchPresentation batchPresentation = ProfileHttpSessionHelper.getProfile(request.getSession()).getActiveBatchPresentation( - "listProcessesDefinitionsHistoryForm"); + BatchPresentationConsts.ID_DEFINITIONS_HISTORY); if (processName != null) { batchPresentation.getFilteredFields().clear(); - batchPresentation.getFilteredFields().put(0, new StringFilterCriteria(processName)); - batchPresentation.setFieldsToSort(new int[] { 1 }, new boolean[] { false }); + int nameFieldIndex = batchPresentation.getType().getFieldIndex(DefinitionHistoryClassPresentation.NAME); + batchPresentation.getFilteredFields().put(nameFieldIndex, new StringFilterCriteria(processName)); + int versionFieldIndex = batchPresentation.getType().getFieldIndex(DefinitionHistoryClassPresentation.VERSION); + batchPresentation.setFieldsToSort(new int[] { versionFieldIndex }, new boolean[] { false }); batchPresentation.setPageNumber(1); } else { ProfileHttpSessionHelper.reloadProfile(request.getSession()); } - return new ActionForward("/definitions_history.do"); + return mapping.findForward(Resources.FORWARD_SUCCESS); } } diff --git a/wfe-web/src/main/java/ru/runa/wf/web/action/ShowDefinitionHistoryDiffAction.java b/wfe-web/src/main/java/ru/runa/wf/web/action/ShowDefinitionHistoryDiffAction.java new file mode 100644 index 0000000000..e9c54db0dd --- /dev/null +++ b/wfe-web/src/main/java/ru/runa/wf/web/action/ShowDefinitionHistoryDiffAction.java @@ -0,0 +1,114 @@ +package ru.runa.wf.web.action; + +import com.cloudbees.diff.Diff; +import com.google.common.base.Charsets; +import com.google.common.collect.Sets; +import com.google.common.io.Files; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionMessage; +import ru.runa.common.web.Commons; +import ru.runa.common.web.Resources; +import ru.runa.common.web.action.ActionBase; +import ru.runa.wf.web.MessagesProcesses; +import ru.runa.wfe.definition.dto.WfDefinition; +import ru.runa.wfe.lang.ProcessDefinition; +import ru.runa.wfe.service.delegate.Delegates; + +/** + * @struts:action path="/show_definitions_history_diff" + */ +public class ShowDefinitionHistoryDiffAction extends ActionBase { + public static final String ACTION = "/show_definitions_history_diff"; + public static final String DEFINITION_NAME = "definitionName"; + public static final String NUM_CONTEXT_LINES = "numContextLines"; + private static final Set EXTENSIONS = Sets.newHashSet("xml", "ftl", "quick", "html", "css", "js"); + + @Override + public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { + String definitionName = request.getParameter(DEFINITION_NAME); + String version1String = request.getParameter("version1"); + String version2String = request.getParameter("version2"); + int numContextLines = Integer.parseInt(request.getParameter(NUM_CONTEXT_LINES)); + if (version1String == null || version2String == null || version1String.equals(version2String)) { + addMessage(request, new ActionMessage(MessagesProcesses.FAILED_VIEW_DIFFERENCES.getKey())); + return mapping.findForward(Resources.FORWARD_FAILURE); + } + WfDefinition definition1 = Delegates.getDefinitionService().getProcessDefinitionVersion(Commons.getUser(request.getSession()), definitionName, Long.valueOf(version1String)); + WfDefinition definition2 = Delegates.getDefinitionService().getProcessDefinitionVersion(Commons.getUser(request.getSession()), definitionName, Long.valueOf(version2String)); + ProcessDefinition processDefinition1 = Delegates.getDefinitionService().getParsedProcessDefinition(Commons.getUser(request.getSession()), definition1.getId()); + ProcessDefinition processDefinition2 = Delegates.getDefinitionService().getParsedProcessDefinition(Commons.getUser(request.getSession()), definition2.getId()); + Set unsortedFileNames = new HashSet<>(); + unsortedFileNames.addAll(processDefinition1.getProcessFiles().keySet()); + unsortedFileNames.addAll(processDefinition2.getProcessFiles().keySet()); + List sortedFileNames = new ArrayList<>(unsortedFileNames); + Collections.sort(sortedFileNames); + StringBuilder b = new StringBuilder(); + for (String fileName : sortedFileNames) { + try { + if (!EXTENSIONS.contains(Files.getFileExtension(fileName))) { + continue; + } + byte[] fileData1 = processDefinition1.getFileData(fileName); + byte[] fileData2 = processDefinition2.getFileData(fileName); + String content1 = fileData1 != null ? new String(fileData1, Charsets.UTF_8) : ""; + String content2 = fileData2 != null ? new String(fileData2, Charsets.UTF_8) : ""; + Diff diff = Diff.diff(new StringReader(content1), new StringReader(content2), true); + if (diff.isEmpty()) { + continue; + } + String unifiedDiff = diff.toUnifiedDiff(version1String + "/" + fileName, version2String + "/" + fileName, // + new StringReader(content1), new StringReader(content2), numContextLines); + String[] lines = unifiedDiff.split("\n", -1); + for (String line : lines) { + boolean fileHeader = line.startsWith("+++") || line.startsWith("---"); + String tdClassName; + if (fileHeader || line.startsWith("@")) { + tdClassName = ""; + } else if (line.startsWith("+")) { + tdClassName = "added"; + } else if (line.startsWith("-")) { + tdClassName = "deleted"; + } else if (line.startsWith("\\")) { + tdClassName = "comment"; + } else { + tdClassName = "unchanged"; + } + b.append(""); + b.append("
");
+                    if (fileHeader) {
+                        b.append("");
+                    }
+                    b.append(StringEscapeUtils.escapeHtml(line));
+                    if (fileHeader) {
+                        b.append("");
+                    }
+                    b.append("
"); + b.append(""); + } + } catch (Exception e) { + log.error("diff", e); + b.append(""); + b.append(fileName).append(": "); + b.append(e.toString()); + b.append(""); + } + } + if (b.length() == 0) { + addMessage(request, new ActionMessage(MessagesProcesses.LABEL_NO_DIFFERENCES_FOUND.getKey())); + } + String diffContent = "" + b + "
"; + request.setAttribute("diffContent", diffContent); + return mapping.findForward(Resources.FORWARD_SUCCESS); + } +} diff --git a/wfe-web/src/main/java/ru/runa/wf/web/tag/ListDefinitionsHistoryFormTag.java b/wfe-web/src/main/java/ru/runa/wf/web/tag/ListDefinitionsHistoryFormTag.java index 2b663d2acc..e624b115d2 100644 --- a/wfe-web/src/main/java/ru/runa/wf/web/tag/ListDefinitionsHistoryFormTag.java +++ b/wfe-web/src/main/java/ru/runa/wf/web/tag/ListDefinitionsHistoryFormTag.java @@ -18,22 +18,25 @@ package ru.runa.wf.web.tag; import java.util.List; - +import org.apache.ecs.html.Form; +import org.apache.ecs.html.Input; import org.apache.ecs.html.TD; import org.tldgen.annotations.BodyContent; - import ru.runa.af.web.BatchPresentationUtils; import ru.runa.common.WebResources; import ru.runa.common.web.PagingNavigationHelper; +import ru.runa.common.web.html.RadioButtonTdBuilder; import ru.runa.common.web.html.ReflectionRowBuilder; import ru.runa.common.web.html.RowBuilder; import ru.runa.common.web.html.SortingHeaderBuilder; -import ru.runa.common.web.html.TdBuilder; import ru.runa.common.web.html.TableBuilder; +import ru.runa.common.web.html.TdBuilder; import ru.runa.common.web.tag.BatchReturningTitledFormTag; import ru.runa.wf.web.MessagesProcesses; +import ru.runa.wf.web.action.ShowDefinitionHistoryDiffAction; import ru.runa.wf.web.html.PropertiesProcessTdBuilder; import ru.runa.wf.web.html.UndeployProcessDefinitionTdBuilder; +import ru.runa.wfe.definition.DefinitionHistoryClassPresentation; import ru.runa.wfe.definition.dto.WfDefinition; import ru.runa.wfe.presentation.BatchPresentation; import ru.runa.wfe.service.DefinitionService; @@ -52,27 +55,49 @@ protected void fillFormElement(TD tdFormElement) { List definitions = definitionService.getDeployments(getUser(), batchPresentation, true); PagingNavigationHelper navigation = new PagingNavigationHelper(pageContext, batchPresentation, count, "/definitions_history.do"); navigation.addPagingNavigationTable(tdFormElement); - TdBuilder[] builders = BatchPresentationUtils.getBuilders(null, batchPresentation, new TdBuilder[] { - new UndeployProcessDefinitionTdBuilder(), new PropertiesProcessTdBuilder() }); - SortingHeaderBuilder headerBuilder = new SortingHeaderBuilder(batchPresentation, 0, 2, getReturnAction(), pageContext); + TdBuilder[] builders = BatchPresentationUtils.getBuilders( + new TdBuilder[] { new RadioButtonTdBuilder("version1", "version"), new RadioButtonTdBuilder("version2", "version") }, + batchPresentation, + new TdBuilder[] { new UndeployProcessDefinitionTdBuilder(), new PropertiesProcessTdBuilder() }); + SortingHeaderBuilder headerBuilder = new SortingHeaderBuilder(batchPresentation, 2, 2, getReturnAction(), pageContext, false); RowBuilder rowBuilder = new ReflectionRowBuilder(definitions, batchPresentation, pageContext, WebResources.ACTION_MAPPING_MANAGE_DEFINITION, getReturnAction(), new DefinitionUrlStrategy(pageContext), builders); tdFormElement.addElement(new TableBuilder().build(headerBuilder, rowBuilder)); navigation.addPagingNavigationTable(tdFormElement); + int nameFieldIndex = batchPresentation.getType().getFieldIndex(DefinitionHistoryClassPresentation.NAME); + String definitionName = batchPresentation.getFieldFilteredCriteria(nameFieldIndex).getFilterTemplate(0); + tdFormElement.addElement(new Input(Input.HIDDEN, ShowDefinitionHistoryDiffAction.DEFINITION_NAME, definitionName)); + tdFormElement.addElement(new Input(Input.HIDDEN, ShowDefinitionHistoryDiffAction.NUM_CONTEXT_LINES, "3")); } @Override protected boolean isSubmitButtonVisible() { - return false; + return true; } @Override protected boolean isSubmitButtonEnabled() { - return false; + return true; } @Override protected String getTitle() { return MessagesProcesses.TITLE_PROCESS_DEFINITIONS.message(pageContext); } + + @Override + public String getAction() { + return ShowDefinitionHistoryDiffAction.ACTION; + } + + @Override + public String getMethod() { + return Form.GET; + } + + @Override + protected String getSubmitButtonName() { + return MessagesProcesses.BUTTON_VIEW_DIFFERENCES.message(pageContext); + } + } diff --git a/wfe-web/src/main/webapp/WEB-INF/classes/struts.properties b/wfe-web/src/main/webapp/WEB-INF/classes/struts.properties index 2c1c0b8b7b..ecf305f980 100644 --- a/wfe-web/src/main/webapp/WEB-INF/classes/struts.properties +++ b/wfe-web/src/main/webapp/WEB-INF/classes/struts.properties @@ -709,3 +709,7 @@ label.payload_parameter_value = Payload parameter value signal.message_is_sent = Signal has been sent view_internal_storage = Internal storage + +button.view_differences = View differences +failed.view_differences = In order to view differences select 2 different versions: left radio for old and right one for new version +label.no_differences_found = No difference has been found diff --git a/wfe-web/src/main/webapp/WEB-INF/classes/struts_ru.properties b/wfe-web/src/main/webapp/WEB-INF/classes/struts_ru.properties index 727dfe76a2..591d5cfd4a 100644 --- a/wfe-web/src/main/webapp/WEB-INF/classes/struts_ru.properties +++ b/wfe-web/src/main/webapp/WEB-INF/classes/struts_ru.properties @@ -466,7 +466,7 @@ permission.UPDATE = \u0418\u0437\u043c\u0435\u043d\u044f\u0442\u044c permission.UPDATE_ACTOR_STATUS = \u0418\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 permission.UPDATE_PERMISSIONS = \u0418\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u043c\u043e\u0447\u0438\u044f permission.VIEW_TASKS = \u0412\u0438\u0434\u0435\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0438 -permission.DELEGATE_TASKS = \u0414\u0435\u043B\u0435\u0433\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0438 +permission.DELEGATE_TASKS = \u0414\u0435\u043b\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0438 process.canceled = \u042d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d process.does.not.exist.error = \u042d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 "{0}" \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 @@ -705,3 +705,7 @@ label.payload_parameter_value = \u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 signal.message_is_sent = \u0421\u0438\u0433\u043d\u0430\u043b \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d view_internal_storage = \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 + +button.view_differences = \u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043e\u0442\u043b\u0438\u0447\u0438\u044f +failed.view_differences = \u0414\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u043e\u0442\u043b\u0438\u0447\u0438\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c 2 \u0440\u0430\u0437\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438: \u0441\u043b\u0435\u0432\u0430 - \u0434\u043b\u044f \u0441\u0442\u0430\u0440\u043e\u0439, \u0441\u043f\u0440\u0430\u0432\u0430 - \u0434\u043b\u044f \u043d\u043e\u0432\u043e\u0439 +label.no_differences_found = \u041c\u0435\u0436\u0434\u0443 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u043c\u0438 \u0432\u0435\u0440\u0441\u0438\u044f\u043c\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e diff --git a/wfe-web/src/main/webapp/WEB-INF/struts-config.xml b/wfe-web/src/main/webapp/WEB-INF/struts-config.xml index 0b75f258e7..4348ccda7a 100644 --- a/wfe-web/src/main/webapp/WEB-INF/struts-config.xml +++ b/wfe-web/src/main/webapp/WEB-INF/struts-config.xml @@ -700,8 +700,14 @@ + + + + + diff --git a/wfe-web/src/main/webapp/WEB-INF/wf/show_process_definitions_history.jsp b/wfe-web/src/main/webapp/WEB-INF/wf/show_process_definitions_history.jsp index 999c41e224..eede8d51b7 100644 --- a/wfe-web/src/main/webapp/WEB-INF/wf/show_process_definitions_history.jsp +++ b/wfe-web/src/main/webapp/WEB-INF/wf/show_process_definitions_history.jsp @@ -16,7 +16,7 @@ <% String returnAction = "/definitions_history.do"; %> - +
diff --git a/wfe-web/src/main/webapp/WEB-INF/wf/show_process_definitions_history_diff.jsp b/wfe-web/src/main/webapp/WEB-INF/wf/show_process_definitions_history_diff.jsp new file mode 100644 index 0000000000..6168c53029 --- /dev/null +++ b/wfe-web/src/main/webapp/WEB-INF/wf/show_process_definitions_history_diff.jsp @@ -0,0 +1,53 @@ +<%@ page language="java" pageEncoding="UTF-8" %> +<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> +<%@ taglib uri="/WEB-INF/wf.tld" prefix="wf" %> +<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> +<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> + +<%@ page import="ru.runa.common.WebResources" %> +<%@ page import="ru.runa.common.web.ProfileHttpSessionHelper" %> +<%@ page import="ru.runa.wfe.presentation.BatchPresentation" %> +<%@ page import="ru.runa.wfe.presentation.filter.StringFilterCriteria" %> + + + + + + + + +
+ <%= request.getAttribute("diffContent") %> +
+ +
+ +
\ No newline at end of file