package com.temenos.interaction.springdsl; /* * #%L * interaction-springdsl * %% * 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.io.File; import java.net.MalformedURLException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import com.temenos.interaction.core.hypermedia.Event; import com.temenos.interaction.core.hypermedia.MethodNotAllowedException; import com.temenos.interaction.core.hypermedia.PathTree; import com.temenos.interaction.core.hypermedia.ResourceState; import com.temenos.interaction.core.hypermedia.ResourceStateProvider; import com.temenos.interaction.core.resource.ConfigLoader; public class SpringDSLResourceStateProvider implements ResourceStateProvider, DynamicRegistrationResourceStateProvider { private final Logger logger = LoggerFactory.getLogger(SpringDSLResourceStateProvider.class); private ConcurrentMap<String, ResourceState> resources = new ConcurrentHashMap<String, ResourceState>(); protected StateRegisteration stateRegisteration; private ConfigLoader configLoader = new ConfigLoader(); /** * Map of ResourceState bean names, to paths. */ protected Properties beanMap; protected boolean initialised = false; /** * Map of paths to state names */ protected Map<String, Set<String>> resourceStatesByPath = new HashMap<String, Set<String>>(); /** * Map of request to state names */ protected Map<String, String> resourceStatesByRequest = new HashMap<String, String>(); /** * Map of resource methods where state name is the key */ protected Map<String, Set<String>> resourceMethodsByState = new HashMap<String, Set<String>>(); /** * Map to a resource path where the state name is the key */ protected Map<String, String> resourcePathsByState = new HashMap<String, String>(); PathTree pathTree = new PathTree(); public SpringDSLResourceStateProvider() {} public SpringDSLResourceStateProvider(Properties beanMap) { this.beanMap = beanMap; } public void setResourceMap(Properties beanMap) { this.beanMap = beanMap; } @Autowired(required = false) public void setConfigLoader(ConfigLoader configLoader) { this.configLoader = configLoader; } protected void initialise() { if (initialised) return; for (Object stateObj : beanMap.keySet()) { storeState(stateObj, null); } initialised = true; } protected void storeState(Object stateObj, String binding) { String stateName = stateObj.toString(); // binding is [GET,PUT /thePath] if (binding == null){ binding = beanMap.getProperty(stateName); } // split into methods and path String[] strs = binding.split(" "); String methodPart = strs[0]; String path = strs[1]; // methods String[] methodsStrs = methodPart.split(","); // path resourcePathsByState.put(stateName, path); // methods Set<String> methodSet = resourceMethodsByState.get(stateName); if (methodSet == null) { methodSet = new HashSet<String>(); } for(String methodStr: methodsStrs) { methodSet.add(methodStr); pathTree.put(path, methodStr, stateName); } resourceMethodsByState.put(stateName, methodSet); for (String method : methodSet) { String request = method + " " + path; logger.debug("Binding ["+stateName+"] to ["+request+"]"); String found = resourceStatesByRequest.get(request); if (found != null) { logger.error("Multiple states bound to the same request ["+request+"], overriding ["+found+"] with ["+stateName+"]"); } resourceStatesByRequest.put(request, stateName); } Set<String> stateNames = resourceStatesByPath.get(path); if (stateNames == null) { stateNames = new HashSet<String>(); } stateNames.add(stateName); resourceStatesByPath.put(path, stateNames); } public void addState(String stateObj, Properties properties) { if (initialised) { String stateName = stateObj.toString(); // binding is [GET,PUT /thePath] String binding = properties.getProperty(stateName); // split into methods and path String[] strs = binding.split(" "); String methodPart = strs[0]; String path = strs[1]; // methods String[] methods = methodPart.split(","); logger.info("Attempting to register state: " + stateName + " methods: " + methods + " path: " + path); // preemptive loading ResourceState state = getResourceState(stateName); if (state != null){ storeState(stateName, binding); Set<String> methodSet = new HashSet<String>(); for(String methodStr: methods) { methodSet.add(methodStr); } } } } public void unload(String name) { resources.remove(name); } @Override public boolean isLoaded(String name) { return resources.containsKey(name); } @Override public ResourceState getResourceState(String resourceStateName) { ResourceState result = null; try { if (resourceStateName != null) { // Try to retrieve the resource state result = resources.get(resourceStateName); if (result == null) { // Resource state has not already been loaded so attempt to load it ResourceStateLoad newState = new ResourceStateLoad(resourceStateName); newState.load(); if ( newState.isLoaded() ) { result = newState.loaded(); } else { logger.error( newState.toString() ); } } } } catch (BeansException e) { logger.error("Failed to load ["+resourceStateName+"]", e); } return result; } @Override public ResourceState determineState(Event event, String resourcePath) { initialise(); String request = event.getMethod() + " " + resourcePath; String stateName = resourceStatesByRequest.get(request); if (stateName != null){ logger.debug("Found state ["+stateName+"] for ["+request+"]"); return getResourceState(stateName); }else{ logger.warn("NOT Found state ["+stateName+"] for ["+request+"]"); return null; } } @Override public Map<String, Set<String>> getResourceStatesByPath() { initialise(); return resourceStatesByPath; } public Map<String, Set<String>> getResourceMethodsByState() { initialise(); return resourceMethodsByState; } public Map<String, String> getResourcePathsByState() { initialise(); return resourcePathsByState; } protected Map<String, Set<String>> getResourceStatesByPath(Properties beanMap) { initialise(); return resourceStatesByPath; } @Override public void setStateRegisteration(StateRegisteration registerState) { this.stateRegisteration = registerState; } /** Load a Resource State from the appropriate location (file or classpath). * There are likely to be several possibilities for where the requested * resource could be: this tracks all of them so that they can be logged * if the resource is not found. */ private class ResourceStateLoad { private String state; private List<String> attempts = new ArrayList<String>(2); private String foundFile; private ResourceState result; /** Define a resource state to load. Must call load() to actually load it. */ public ResourceStateLoad( String resourceStateName ) { state = resourceStateName; } /** Was the load operation successful? */ public boolean isLoaded() { return ( result != null ); } /** Get the Resource State from a successful load */ public ResourceState loaded() { return result; } /** Description of the state of this load, intended for logging */ public String toString() { if ( isLoaded() ) { return "Loaded Resource State " + state + " from " + foundFile; } else if (attempts.size()==0) { return "Not-loaded Resource State " + state; } else if (foundFile != null ) { return "State " + state + " not found in " + foundFile; } StringBuilder msg = new StringBuilder( "Failed to load resource state " ); msg.append( state ); msg.append( ". Attempted to load from " ); for ( int i = 0 ; i < attempts.size() ; ++i ) { if ( i > 0 ) msg.append(", "); msg.append("["); msg.append(attempts.get(i)); msg.append("]"); } return msg.toString(); } /** Load the configured resource state. * Use this method only once. * call isLoaded() to discover success or failure */ public void load() { // check that this has not been called before if ( attempts.size() > 0 ) throw new IllegalStateException( "repeated call to load()" ); String tmpResourceStateName = state; String tmpResourceName = tmpResourceStateName; if(tmpResourceName.contains("-")) { tmpResourceName = tmpResourceName.substring(0, tmpResourceName.indexOf("-")); } String beanXml = "IRIS-" + tmpResourceName + "-PRD.xml"; // Attempt to create Spring context based on current resource filename pattern ApplicationContext context = createApplicationContext(beanXml); if (context == null) { // Failed to create Spring context using current resource filename pattern so use old pattern int pos = tmpResourceName.lastIndexOf("_"); if (pos > 3){ tmpResourceName = tmpResourceName.substring(0, pos); beanXml = "IRIS-" + tmpResourceName + "-PRD.xml"; context = createApplicationContext(beanXml); if (context != null) { // Successfully created Spring context using old resource filename pattern // Convert resource state name to old resource name format pos = tmpResourceStateName.lastIndexOf("-"); if (pos < 0){ pos = tmpResourceStateName.lastIndexOf("_"); if (pos > 0){ tmpResourceStateName = tmpResourceStateName.substring(0, pos) + "-" + tmpResourceStateName.substring(pos+1); } } } } } if(context != null) { result = loadAllResourceStatesFromFile(context, tmpResourceStateName); } } private ResourceState loadAllResourceStatesFromFile(ApplicationContext context, String resourceState) { Map<String,ResourceState> tmpResources = context.getBeansOfType(ResourceState.class); // Save all the loaded resources into the main resource state cache resources.putAll(tmpResources); ResourceState result = null; if(tmpResources.containsKey(resourceState)) { result = tmpResources.get(resourceState); } return result; } /** * @param beanXml the filename to locate * @return a Spring ApplicationContext */ private ApplicationContext createApplicationContext(String beanXml) { ApplicationContext result = null; if(configLoader.getIrisConfigDirPaths().isEmpty()) { // Try and load the resource from the classpath String description = "classpath:" + beanXml; attempts.add(description); result = new ClassPathXmlApplicationContext(new String[] {beanXml}); foundFile = description; } else { // Try and load the resource from the file system as a resource directories has been specified for(String directoryPath : configLoader.getIrisConfigDirPaths()) { Path filePath = Paths.get(directoryPath, beanXml); result = createApplicationContext(new File(filePath.toString())); if (result != null) { break; } } } return result; } private ApplicationContext createApplicationContext(File file) { URL fileURL = resolveFileURL(file); if (fileURL == null) { return null; } attempts.add(fileURL.toString()); if (file.exists()) { foundFile = fileURL.toString(); return new FileSystemXmlApplicationContext(new String[] { fileURL.toString() }); } return null; } private URL resolveFileURL(File file) { try { return file.toURI().toURL(); } catch (MalformedURLException e) { logger.error("Failed to resolve URL for file: " + file.getAbsolutePath(), e); return null; } } } @Override public ResourceState getResourceState(String httpMethod, String url) throws MethodNotAllowedException { String resourceStateId = getResourceStateId(httpMethod, url); if(resourceStateId == null) { if(pathTree.get(url) != null) { Set<String> allowedMethods = pathTree.get(url).keySet(); throw new MethodNotAllowedException(allowedMethods); } else { return null; } } return getResourceState(resourceStateId); } public String getResourceStateId(String httpMethod, String url) throws MethodNotAllowedException { Map<String,String> methodToState = null; initialise(); methodToState = pathTree.get(url); String resourceStateId = null; if(methodToState != null) { resourceStateId = methodToState.get(httpMethod); if(resourceStateId == null) { if(pathTree.get(url) != null) { Set<String> allowedMethods = pathTree.get(url).keySet(); throw new MethodNotAllowedException(allowedMethods); } } } else { return null; } return resourceStateId; } }