package com.temenos.interaction.core.hypermedia; /* * #%L * interaction-core * %% * Copyright (C) 2012 - 2016 Temenos Holdings N.V. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; /** * Implementation of {@link LinkToFieldAssociation} */ public class LinkToFieldAssociationImpl implements LinkToFieldAssociation { private static final Pattern COLLECTION_PARAM_PATTERN = Pattern.compile("\\{{1}([a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)+)\\}{1}"); private static final String PARAM_REPLACEMENT_REGEX = "\\((\\d+)\\)"; private static final Logger logger = LoggerFactory.getLogger(LinkToFieldAssociationImpl.class); private Transition transition; private String targetFieldName; private List<String> transitionCollectionParams; private Boolean hasCollectionDynamicResourceName; private Map<String, Object> transitionProperties; private Map<String, Object> normalisedTransitionProperties; public LinkToFieldAssociationImpl(Transition transition, Map<String, Object> properties) { this.transition = transition; targetFieldName = transition.getSourceField(); transitionCollectionParams = getCollectionParams(transition); hasCollectionDynamicResourceName = hasCollectionDynamicResourceName(); transitionProperties = properties; normalisedTransitionProperties = HypermediaTemplateHelper.normalizeProperties(properties); } @Override public boolean isTransitionSupported() { if (targetFieldName == null && (!transitionCollectionParams.isEmpty() || hasCollectionDynamicResourceName)) { logger.error("Cannot generate links for transition " + transition + ". Target field name cannot be null if we have collection parameters or a collection dymamic resource."); return false; } return true; } @Override public List<LinkProperties> getProperties() { List<LinkProperties> transitionPropertiesList = new ArrayList<LinkProperties>(); //Get list of child names, i.e. For AB.CD and AA.ZZ add CD, ZZ to list List<String> childParamNames = new ArrayList<String>(); for (String collectionParam : transitionCollectionParams) { childParamNames.add(getChildNameOfCollectionValue(collectionParam)); } if (targetFieldName != null && targetFieldName.contains(".")) // Multivalue target { createLinkPropertiesForMultivalueTarget(transitionPropertiesList, childParamNames); } else // Non multivalue target { createLinkPropertiesForSingleTarget(transitionPropertiesList, childParamNames); } logger.debug("Created " + transitionPropertiesList.size() + " properties map(s) for transition " + transition); return transitionPropertiesList; } private void createLinkPropertiesForMultivalueTarget(List<LinkProperties> transitionPropertiesList, List<String> childParamNames) { List<String> targetFields = extractMatchingFieldsFromTransitionProperties(targetFieldName); //If properties does not contain the target, add unresolved target if(targetFields.isEmpty()) { targetFields.add(targetFieldName); } String parentTargetFieldName = getParentNameOfCollectionValue(targetFieldName); boolean hasSameParent = false; if (transitionCollectionParams.size() > 0) { ListIterator<String> transCollIter = transitionCollectionParams.listIterator(); //Determine if parent of target field and parent of parameters are same while(transCollIter.hasNext() && (hasSameParent = StringUtils.equals(getParentNameOfCollectionValue(transCollIter.next()), parentTargetFieldName))); } int targetFieldIndex = 0; for (String targetField : targetFields) // Generate one or more map of properties for each target field { List<String> resolvedDynamicResourceFieldNames = getDynamicResourceResolvedFieldName(transition.getTarget(), targetField); if (transitionCollectionParams.isEmpty()) // Create one link properties map per target { LinkProperties linkProps = createLinkProperties(targetField, resolvedDynamicResourceFieldNames, childParamNames, null); transitionPropertiesList.add(linkProps); } else if (hasSameParent) // Create one link properties map per target { String parentResolvedTargetFieldName = getParentNameOfCollectionValue(targetField); LinkProperties linkProps = createLinkProperties(targetField, resolvedDynamicResourceFieldNames, childParamNames, parentResolvedTargetFieldName); transitionPropertiesList.add(linkProps); } else { LinkProperties linkProps = createLinkPropertieswithResolvedParentSet(targetField, resolvedDynamicResourceFieldNames, childParamNames, getParentSetOfMultivalueChildren(targetFieldIndex++)); transitionPropertiesList.add(linkProps); } } } private void createLinkPropertiesForSingleTarget(List<LinkProperties> transitionPropertiesList, List<String> childParamNames) { if (transitionCollectionParams.isEmpty()) // Create one link properties map per target { LinkProperties linkProps = new LinkProperties(targetFieldName, transitionProperties); transitionPropertiesList.add(linkProps); } else { int numOfChildren = getNumberOfMultivalueChildren(); if(allParametersHaveSameParent(transitionCollectionParams.toArray(new String[0]))){ String parentResolvedName = getParentOfMultivalueChildren(); // Create multiple properties maps per target. Depends on the number of children in the entity in transition properties for (int i = 0; i <= numOfChildren; i++) { String childParentResolvedParamNewIndex = parentResolvedName + "(" + i + ")"; LinkProperties linkProps = createLinkProperties(targetFieldName, null, childParamNames, childParentResolvedParamNewIndex); transitionPropertiesList.add(linkProps); } } else { for (int i = 0; i <= numOfChildren; i++) { LinkProperties linkProps = createMultiLinkProperties(targetFieldName, null, childParamNames, null,i); transitionPropertiesList.add(linkProps); } } } } private LinkProperties createMultiLinkProperties(String targetField, List<String> resolvedDynamicResourceFieldNames, List<String> childParamNames, String resolvedParentName, int index){ List<String> resolvedDynamicFileds = new ArrayList<String>(); for(String collectionParam : transitionCollectionParams){ String parentName = getParentNameOfCollectionValue(collectionParam) + "(" + index + ")"; String childParam = getChildNameOfCollectionValue(collectionParam); resolvedDynamicFileds.add(parentName+"."+childParam); } LinkProperties linkProps = createLinkProperties(targetFieldName, resolvedDynamicFileds, childParamNames, null); return linkProps; } private LinkProperties createLinkProperties(String targetField, List<String> resolvedDynamicResourceFieldNames, List<String> childParamNames, String resolvedParentName) { Set<String> resolvedParentSet = new HashSet<String>(); resolvedParentSet.add(resolvedParentName); return createLinkPropertieswithResolvedParentSet(targetField, resolvedDynamicResourceFieldNames, childParamNames, resolvedParentSet); } private LinkProperties createLinkPropertieswithResolvedParentSet(String targetField, List<String> resolvedDynamicResourceFieldNames, List<String> childParamNames, Set<String> resolvedParentSet) { List<String> paramPropertyKeys = new ArrayList<String>(); for (String resolvedParentName : resolvedParentSet) { if (StringUtils.isNotBlank(resolvedParentName)) { paramPropertyKeys.addAll(getListOfParamPropertyKeys(childParamNames, resolvedParentName)); } } if (!CollectionUtils.isEmpty(resolvedDynamicResourceFieldNames)) { paramPropertyKeys.addAll(resolvedDynamicResourceFieldNames); } LinkProperties linkProps = new LinkProperties(targetField, transitionProperties); addEntriesToLinkProperties(linkProps, paramPropertyKeys); return linkProps; } private List<String> getListOfParamPropertyKeys(List<String> childParamNames, String fullyQualifiedParentName) { List<String> propertyKeys = new ArrayList<String>(); for (String childParam : childParamNames) { propertyKeys.add(fullyQualifiedParentName + "." + childParam); } return propertyKeys; } private List<String> getDynamicResourceResolvedFieldName(ResourceState targetResourceState, String targetField) { List<String> resolvedFieldNames = new ArrayList<String>(); if (hasCollectionDynamicResourceName) { String targetFieldParentName = getParentNameOfCollectionValue(targetField); if (targetResourceState instanceof DynamicResourceState) { String[] args = ArrayUtils.nullToEmpty(((DynamicResourceState) targetResourceState).getResourceLocatorArgs()); for(int index = 0; args.length > index ; index++ ){ String argValue = args[index]; if(StringUtils.contains(argValue, ".")){ resolvedFieldNames.add(targetFieldParentName + "." + getChildNameOfCollectionValue(argValue).replaceAll("\\}", "")); } } } } return resolvedFieldNames; } private int getNumberOfMultivalueChildren() { //Find the number of children using the index of the parent in transition properties //i.e. if we have A(0).B and A(1).B in the properties map, return 1 int numChildren = 0; for (String collectionParam : transitionCollectionParams) { List<String> entityParamFields = extractMatchingFieldsFromTransitionProperties(collectionParam); for (String entityParam : entityParamFields) { int index = Integer.parseInt(entityParam.substring(entityParam.lastIndexOf("(") + 1, entityParam.lastIndexOf(")"))); if (index > numChildren) { numChildren = index; } } } return numChildren; } private String getParentOfMultivalueChildren() { String collectionParam = transitionCollectionParams.get(0); List<String> matchingFields = extractMatchingFieldsFromTransitionProperties(collectionParam); String parent = getParentNameOfCollectionValue(matchingFields.get(0)); return parent.substring(0, parent.lastIndexOf("(")); } private Set<String> getParentSetOfMultivalueChildren(int targetFieldIndex) { Set<String> parentSet = new HashSet<>(); for (String collectionParam : transitionCollectionParams) { List<String> matchingFields = extractMatchingFieldsFromTransitionProperties(collectionParam); if(!matchingFields.isEmpty()){ String parent = getParentNameOfCollectionValue(matchingFields.get(0)); parentSet.add(parent.substring(0, parent.lastIndexOf("(")).concat("(" + targetFieldIndex + ")")); } } return parentSet; } private List<String> getCollectionParams(Transition transition) { List<String> collectionParams = new ArrayList<String>(); Map<String, String> transitionUriMap = transition.getCommand().getUriParameters(); if (transitionUriMap == null) { return collectionParams; } for (Map.Entry<String, String> entry : transitionUriMap.entrySet()) { String parameter = entry.getValue(); Matcher matcher = COLLECTION_PARAM_PATTERN.matcher(parameter); while (matcher.find()) { collectionParams.add(matcher.group(1)); } } if (transition.getTarget() instanceof DynamicResourceState) { String[] args = ArrayUtils.nullToEmpty(((DynamicResourceState) transition.getTarget()).getResourceLocatorArgs()); for (String parameter : args) { Matcher matcher = COLLECTION_PARAM_PATTERN.matcher(parameter); while (matcher.find()) { collectionParams.add(matcher.group(1)); } } } return collectionParams; } private boolean hasCollectionDynamicResourceName() { return getFirstParentNameOfDynamicResource() != null ? true : false; } private String getFirstParentNameOfDynamicResource() { String firstParent = null; if (transition.getTarget() instanceof DynamicResourceState) { String[] args = ArrayUtils.nullToEmpty(((DynamicResourceState) transition.getTarget()).getResourceLocatorArgs()); for (String argValue : args) { if(StringUtils.contains(argValue, ".")){ firstParent = StringUtils.replace(getParentNameOfCollectionValue(argValue), "{", "");//Remove starting curly brace { break; } } } return firstParent; } private List<String> extractMatchingFieldsFromTransitionProperties(String fieldName) { List<String> matchingFieldList = new ArrayList<String>(); for (String key : normalisedTransitionProperties.keySet()) { if (StringUtils.equals(fieldName, key.replaceAll(PARAM_REPLACEMENT_REGEX, ""))) { matchingFieldList.add(key); } } return matchingFieldList; } private String getParentNameOfCollectionValue(String value) { if (StringUtils.isNotBlank(value) && value.contains(".")) { return value.substring(0, value.lastIndexOf(".")); } return null; } private String getChildNameOfCollectionValue(String value) { if (StringUtils.isNotBlank(value) && value.contains(".")) { return value.substring(value.lastIndexOf(".") + 1); } return null; } private boolean allParametersHaveSameParent(String... parameters) { boolean haveSameParent = true; List<String> parents = new ArrayList<String>(); for (String param : parameters) { String parent = getParentNameOfCollectionValue(param); if (parent == null) { //Do nothing. Dealing with non multivalue field } else if (parents.isEmpty()) { parents.add(parent); } else if (!parents.contains(parent)) { haveSameParent = false; break; } } return haveSameParent; } private void addEntriesToLinkProperties(LinkProperties linkProps, List<String> keys) { Map<String, Object> linkPropertiesMap = linkProps.getTransitionProperties(); Set<String> unindexedKeys = new HashSet<String>(); for (String key : keys) { Object value = this.normalisedTransitionProperties.get(key); String unindexedKey = key.replaceAll(PARAM_REPLACEMENT_REGEX, ""); unindexedKeys.add(unindexedKey); if (value != null && value instanceof String) { linkPropertiesMap.put(unindexedKey, value); } else { linkPropertiesMap.put(unindexedKey, ""); } } // Replace Id={A.B.C} with Id={VAL} if A.B.C=VAL is in the linkPropertiesMap for (String key : linkPropertiesMap.keySet()) { Object value = linkPropertiesMap.get(key); if (value instanceof String) { String replacementKey = ((String) value).replaceAll("\\{", "").replaceAll("\\}", ""); if (unindexedKeys.contains(replacementKey)) { String replacementValue = ((String) value).replaceAll("\\{" + Pattern.quote(replacementKey) + "\\}", linkPropertiesMap.get(replacementKey).toString()); linkPropertiesMap.put(key, replacementValue); } } } } }