1 /* 2 * Copyright 2014 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.openehealth.ipf.commons.xml; 17 18 import javax.xml.transform.stream.StreamSource; 19 import java.io.IOException; 20 import java.net.MalformedURLException; 21 import java.net.URL; 22 import java.util.Map; 23 import java.util.concurrent.ConcurrentMap; 24 25 /** 26 * Abstract parent class for XML validators, transmogrifiers, and other classes 27 * which cache static external resources in memory. 28 * <br> 29 * On the basis of validator/transmogrifier parameters (<code>Object... params</code>), 30 * instances of this class determine: 31 * <ul> 32 * <li>Location of the raw resource (i.e. an unparsed XSLT document as opposed 33 * to its compiled representation in memory) — method {@link #resourceLocation(Object...)}, 34 * <li>Parameters of resource instantiation (compilation) and execution (application) — 35 * method {@link #resourceParameters(Object...)}, 36 * <li>Key to store the compiled resource in the cache — 37 * method {@link #resourceCacheKey(Object...)}. 38 * </ul> 39 * The main method to get a ready-to-use resource instance is {@link #resource(Object...)}, 40 * which takes over instantiation, initialization, caching, and other aspects of resource 41 * lifecycle. For resource instantiation step, it calls the {@link #createResource(Object...)} 42 * method, which is truly resource-type specific and thus declared as <code>abstract</code> 43 * in the given base class. 44 * 45 * @param <T> resource type. 46 */ 47 abstract public class AbstractCachingXmlProcessor<T> { 48 49 public static final String RESOURCE_LOCATION = "org.openehealth.ipf.commons.xml.ResourceLocation"; 50 51 private final ClassLoader classLoader; 52 53 protected static abstract class Loader<S> { 54 55 private S loaded; 56 57 abstract protected S load() throws RuntimeException; 58 59 synchronized S get() { 60 if (loaded == null) loaded = load(); 61 return loaded; 62 } 63 } 64 65 /** 66 * Constructor. 67 * 68 * @param classLoader class loader, may be <code>null</code>. 69 */ 70 protected AbstractCachingXmlProcessor(ClassLoader classLoader) { 71 super(); 72 this.classLoader = classLoader == null ? this.getClass().getClassLoader() : classLoader; 73 } 74 75 /** 76 * @return static cache for the configured resource type. 77 * Note that the returned Map is not necessarily synchronized. 78 */ 79 abstract protected ConcurrentMap<String, Loader<T>> getCache(); 80 81 /** 82 * Extracts (constructs) resource location from validator/transmogrifier parameters. 83 * 84 * @param params validator/transmogrifier parameters. 85 * @return resource location as a String. 86 */ 87 protected String resourceLocation(Object... params) { 88 if (params[0] instanceof String) { 89 return (String) params[0]; 90 } else if (params[0] instanceof Map) { 91 return (String) ((Map<?, ?>) params[0]).get(RESOURCE_LOCATION); 92 } else if (params[0] instanceof SchematronProfile) { 93 SchematronProfile p = (SchematronProfile) params[0]; 94 return p.getRules(); 95 } 96 throw new IllegalStateException("Cannot extract resource location"); 97 } 98 99 /** 100 * Extracts (constructs) resource cache key validator/transmogrifier parameters. 101 * <p/> 102 * Per default, the key equals to the resource location. 103 * 104 * @param params validator/transmogrifier parameters. 105 * @return cache key as a String. 106 */ 107 protected String resourceCacheKey(Object... params) { 108 return resourceLocation(params); 109 } 110 111 /** 112 * Extracts (constructs) resource creation/application parameters from 113 * validator/transmogrifier parameters. 114 * 115 * @param params validator/transmogrifier parameters. 116 * @return resource creation/application parameters as a Map, 117 * or <code>null</code> if not found. 118 */ 119 protected Map<String, Object> resourceParameters(Object... params) { 120 if (params[0] instanceof Map) { 121 return (Map<String, Object>) params[0]; 122 } else if (params[0] instanceof SchematronProfile) { 123 SchematronProfile p = (SchematronProfile) params[0]; 124 return p.getParameters(); 125 } else if (params.length > 1 && params[1] instanceof Map) { 126 return (Map<String, Object>) params[1]; 127 } 128 return null; 129 } 130 131 /** 132 * Retrieves from the cache and returns the resource with the given location and 133 * further attributes. If necessary, creates the resource by means of 134 * {@link #createResource(Object...)} and stores it into the cache. 135 * The cache key is a combination of the location and further attributes. 136 * <p/> 137 * This method MUST be re-entrant, its result MUST be thread-safe. 138 * 139 * @param params validator/transmogrifier parameters. 140 * @return resource instance. 141 * @throws Exception 142 */ 143 protected T resource(final Object... params) throws Exception { 144 String key = resourceCacheKey(params); 145 getCache().putIfAbsent(key, new Loader<T>() { 146 @Override 147 protected T load() throws RuntimeException { 148 return createResource(params); 149 } 150 }); 151 return getCache().get(key).get(); 152 } 153 154 /** 155 * Creates a ready-to-use resource (e.g. an XML Schema instance) for the given key. 156 * Insertion into the cache will happen externally, this method's purpose 157 * is only to instantiate the resource to be cached. 158 * <p/> 159 * This method does not need to be re-entrant, but its result MUST be thread-safe. 160 * 161 * @param params validator/transmogrifier parameters. 162 * @return resource of the configured type. 163 */ 164 abstract protected T createResource(Object... params); 165 166 /** 167 * Loads the resource into memory and returns it as a Stream. 168 * <p/> 169 * This method does not need to be re-entrant. 170 * 171 * @param params validator/transmogrifier parameters. 172 * @return resource of the configured type. 173 */ 174 protected StreamSource resourceContent(Object... params) { 175 String location = resourceLocation(params); 176 URL url; 177 try { 178 if (location.startsWith("/")) { 179 url = getClass().getResource(location); 180 } else { 181 try { 182 // Try to parse the location as a URL... 183 url = new URL(location); 184 } catch (MalformedURLException ex) { 185 // No URL -> resolve as resource path. 186 url = getClass().getClassLoader().getResource(location); 187 if (url == null) throw new IOException("Location not found"); 188 } 189 } 190 return new StreamSource(url.openStream(), url.toExternalForm()); 191 } catch (IOException e) { 192 throw new IllegalArgumentException("The resource " + location + " is not valid", e); 193 } 194 } 195 196 }