View Javadoc
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) &mdash; method {@link #resourceLocation(Object...)},
34   * <li>Parameters of resource instantiation (compilation) and execution (application) &mdash;
35   * method {@link #resourceParameters(Object...)},
36   * <li>Key to store the compiled resource in the cache &mdash;
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 }