package com.temenos.interaction.core.hypermedia; /* * #%L * interaction-core * %% * Copyright (C) 2012 - 2014 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.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriBuilder; import org.odata4j.core.OCollection; import org.odata4j.core.OComplexObject; import org.odata4j.core.OObject; import org.odata4j.core.OProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.temenos.interaction.core.MultivaluedMapImpl; public class HypermediaTemplateHelper { private static final Logger LOGGER = LoggerFactory.getLogger(HypermediaTemplateHelper.class); // regex for uri template pattern static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{(.*?)\\}"); /** * Provide path parameters for a transition's target state. * @param transition transition * @param transitionProperties transition properties * @return path parameters */ public static MultivaluedMap<String, String> getPathParametersForTargetState(Transition transition, Map<String, Object> transitionProperties) { //Parse source and target parameters from the transition's 'path' and 'originalPath' attributes respectively MultivaluedMap<String, String> pathParameters = new MultivaluedMapImpl<String>(); String resourcePath = transition.getTarget().getPath(); String[] sourceParameters = getPathTemplateParameters(resourcePath); String[] targetParameters = getPathTemplateParameters(resourcePath); //Apply transition properties to parameters for(int i=0; i < sourceParameters.length; i++) { Object paramValue = transitionProperties.get(sourceParameters[i]); if(paramValue != null) { pathParameters.putSingle(targetParameters[i], paramValue.toString()); } } return pathParameters; } /* * Returns the list of parameters contained inside * a URI template. */ public static String[] getPathTemplateParameters(String pathTemplate) { List<String> params = new ArrayList<String>(); Matcher m = TEMPLATE_PATTERN.matcher(pathTemplate); while(m.find()) { params.add(m.group(1)); } return params.toArray(new String[0]); } /** * Similar to UriBuilder, but used for simple template token replacement where * supplied template is not a uri, and not all tokens need to be replaced. * @param template * @param properties * @return */ public static String templateReplace(String template, Map<String, Object> properties) { String result = template; Map<String, Object> normalizedProperties = null; try { if (template != null) { Matcher m = TEMPLATE_PATTERN.matcher(template); while(m.find()) { String param = m.group(1); if(null == normalizedProperties) { normalizedProperties = HypermediaTemplateHelper.normalizeProperties(properties); } if (normalizedProperties.containsKey(param)) { // replace template tokens result = result.replaceAll(Pattern.quote("{"+param+"}"), Matcher.quoteReplacement(normalizedProperties.get(param).toString())); } } } } catch (Exception e) { LOGGER.error("An error occurred while replacing tokens in ["+template+"]", e); } return result; } /** * Given a base uri template and a uri, return the base uri portion. * @param baseUriTemplate * @param baseUri * @return */ public static String getTemplatedBaseUri(String baseUriTemplate, String uri) { // (\Qhttp://localhost:8080/responder/rest/\E)((?<companyid>[^\/]+))(\Q/\E)((?<href>[^\/]+)) // form a regex for the base uri including any context parameters StringBuffer templateRegex = new StringBuffer(); Matcher m = TEMPLATE_PATTERN.matcher(baseUriTemplate); if (m.find() && m.groupCount() > 0) { templateRegex // match anything before the template .append(".?") // match on the provided template .append(Pattern.quote(baseUriTemplate.substring(0, m.start()))); templateRegex.append(getUriTemplatePattern(m.group(1))); while(m.find()) { templateRegex.append(getUriTemplatePattern(m.group(1))); } } else { templateRegex // match anything before the template .append(".?") // match on the provided template .append(Pattern.quote(baseUriTemplate)); } String groups[] = getPathTemplateParameters(baseUriTemplate); Map<String, Object> values = match(groups, templateRegex.toString(), uri); String result = null; if (values != null) { UriBuilder builder = UriBuilder.fromPath(baseUriTemplate); result = builder.buildFromMap(values).toASCIIString(); } else { result = baseUriTemplate; } if (!result.endsWith("/")) { result += "/"; } return result; } private static Map<String, Object> match(String groups[], String template, String uri) { Pattern p = Pattern.compile(template); Matcher m = p.matcher(uri); if (m.find()) { Map<String, Object> params = new HashMap<String, Object>(m.groupCount()); for (int g = 0; g < m.groupCount() && g < groups.length; g++) { params.put(groups[g], m.group(1)); } return params; } return null; } private static String getUriTemplatePattern(String param) { // works with Java 7 return "([^\\/]*)"; } /** * <p>Returns a map having the original properties plus new entries created from properties in OCollections. * * <p>Example: For a map having value [OCollection (A) -> OComplexType (B) -> OProperty(C) -> value D] * this method will generate a map entry with key A.B.C and value D * @param properties * @return */ public static LinkedHashMap<String, Object> normalizeProperties(Map<String, Object> properties) { LinkedHashMap<String, Object> propertiesNormalized = new LinkedHashMap <String, Object>(); for (Map.Entry<String, Object> entry : properties.entrySet()) { if (entry.getValue() instanceof OCollection) { OCollection<?> collection = (OCollection<?>) entry.getValue(); String collectionName = extractSimplePropertyName(entry.getKey()); buildComplexPropertyEntry(propertiesNormalized, collection, collectionName); } else { addUniqueMapEntry(propertiesNormalized, entry.getKey(), entry.getValue()); } } return propertiesNormalized; } /** * Extracts single name of a property out of a fully qualified property name. * A fully qualified property name is made of an optional prefix followed by a mandatory * single property name. They are separated by "_" if prefix is present. * * @param fullyQualifiedName the fully qualified name of property * * @return the single name of property * */ public static String extractSimplePropertyName(String fullyQualifiedName) { if (fullyQualifiedName == null || fullyQualifiedName.isEmpty()) { return null; } if (!fullyQualifiedName.contains("_")) { return fullyQualifiedName; } return fullyQualifiedName.substring(fullyQualifiedName.indexOf("_") + 1, fullyQualifiedName.length()); } private static void buildComplexPropertyEntry(LinkedHashMap<String, Object> propertiesNormalized, OCollection<?> collection, String collectionName) { int elementIdx = 0; for (OObject each : collection) { if (each instanceof OComplexObject) { OComplexObject ooComplex = (OComplexObject) each; for (OProperty<?> property : ooComplex.getProperties()) { StringBuilder complexMvPropertyName = new StringBuilder(); complexMvPropertyName.append(collectionName).append("(").append(elementIdx).append(")").append("."); complexMvPropertyName.append(extractSimplePropertyName(property.getName())); if (property.getValue() instanceof OCollection) { buildComplexPropertyEntry(propertiesNormalized, (OCollection<?>) property.getValue(), complexMvPropertyName.toString()); } else { addUniqueMapEntry(propertiesNormalized, complexMvPropertyName.toString(), property.getValue()); } } } else { //Primitive or Enum StringBuilder complexMvPropertyName = new StringBuilder(); complexMvPropertyName.append(collectionName).append("(").append(elementIdx).append(")"); addUniqueMapEntry(propertiesNormalized, complexMvPropertyName.toString(), each); } elementIdx++; } } private static void addUniqueMapEntry(LinkedHashMap<String, Object> propertiesNormalized, String key, Object object) { if (!propertiesNormalized.containsKey(key)) { propertiesNormalized.put(key, object); } } }