View Javadoc
1   /*
2    * #%L
3    * Nuiton Utils
4    * %%
5    * Copyright (C) 2004 - 2010 CodeLutin
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  
23  package org.nuiton.util;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import javax.xml.transform.Source;
29  import javax.xml.transform.Transformer;
30  import javax.xml.transform.TransformerFactory;
31  import javax.xml.transform.URIResolver;
32  import javax.xml.transform.stream.StreamSource;
33  import java.net.URL;
34  import java.net.URLClassLoader;
35  import java.util.HashSet;
36  import java.util.List;
37  import java.util.Set;
38  import java.util.SortedMap;
39  import java.util.TreeMap;
40  import java.util.regex.Matcher;
41  import java.util.regex.Pattern;
42  
43  /**
44   * <p>
45   * ResourceResolver is a URIResolver for XSL transformation.
46   * </p>
47   * <p>
48   * Its purpose is to catch the XSL document(...) function call and return a
49   * valid data source only if the wanted resource is present in the project
50   * resources.
51   * </p>
52   * <p>The main goal of ResourceResolver is to retrieve datasources locally, if the
53   * wanted resource is not present in project resource then null will be
54   * returned.
55   * </p>
56   * The resolve function search for the file part of href parameter:
57   * <ul>
58   * <li>href: http://argouml.org/profiles/uml14/default-uml14.xmi</li>;
59   * <li>file part: default-uml14.xmi.</li>
60   * </ul>
61   * The resource is searched this way:
62   * <ul>
63   * <li>eg: {@code [file part extension]/[file part]}</li>
64   * <li>eg: {@code xmi/default-uml14.xmi}</li>
65   * </ul>
66   *
67   * You should assign this ResourceResolver on
68   * {@link Transformer} but not on {@link TransformerFactory}.
69   *
70   * @author chorlet
71   */
72  public class ResourceResolver implements URIResolver {
73  
74      /** log. */
75      private static final Log log = LogFactory.getLog(ResourceResolver.class);
76  
77      /** Shared Cache to not search in full classpath at each request. */
78      protected static final SortedMap<String, Source> sourceCache =
79              new TreeMap<String, Source>();
80  
81      /** Shared Cache of not local resources */
82      protected static final Set<String> unresolvedCache = new HashSet<String>();
83  
84      /** le pattern de detection d'une uri */
85      public static final Pattern HREF_PATTERN =
86              Pattern.compile("([a-zA-Z]+)\\:\\/\\/(.+)");
87  
88      /** Pour vider le cache partage. */
89      public static synchronized void clearCache() {
90          sourceCache.clear();
91          unresolvedCache.clear();
92      }
93  
94      protected String base;
95  
96      /** le niveau de verbosite */
97      protected boolean verbose = log.isDebugEnabled();
98  
99      /** le classe loader utilise pour recuperer les resources */
100     protected ClassLoader cl = getClass().getClassLoader();
101 
102     public ResourceResolver() {
103         this(null);
104     }
105 
106     public ResourceResolver(String base) {
107         if (base != null && base.endsWith("/") && base.length() > 1) {
108             base = base.substring(0, base.length() - 1);
109         }
110         this.base = base;
111         if (log.isTraceEnabled()) {
112             log.trace(this + ", base : " + this.base);
113         }
114     }
115 
116     /**
117      * Resolve href on local resource.
118      *
119      * @return null if local resource not found
120      */
121     @Override
122     public synchronized Source resolve(String href, String base) {
123 
124         if (unresolvedCache.contains(href)) {
125             // href was already unfound in class-path,
126             // do not search twice (class-path search can be expensive)
127             if (verbose) {
128                 log.info("Skip unresolved " + href);
129             }
130             return null;
131         }
132 
133         if (sourceCache.containsKey(href)) {
134             // directly use the cached source, skip all other stuff
135             if (verbose) {
136                 log.info("use cached source " + href);
137             }
138             return sourceCache.get(href);
139         }
140 
141         // at this point, the href is undiscovered, try to find in in class-path
142 
143 
144         if (verbose) {
145             log.info("Resolving " + href);
146         }
147 
148         // URI : 
149         // example 1 : pathmap://UML_METAMODELS/UML.metamodel.uml
150         // example 2 : http://argouml.org/profiles/uml14/default-java.xmi
151 
152         // relative path :
153         // example 3 : xxx/zzz/ttt.uml
154 
155         Source source;
156 
157         // if URI
158         Matcher matcher = HREF_PATTERN.matcher(href);
159         if (matcher.matches()) {
160 //            String protocol = matcher.group(1);
161             String path = matcher.group(2);
162             // try look only with the filename
163             // this is the last chance to find something :)
164             source = findHrefSource(path);
165         } else {
166 
167             // no protocol, so should be a relative path location
168             source = findRelativeSource(href);
169         }
170 
171         if (source == null) {
172             // means this resolver was not able to find the source
173             if (verbose) {
174                 log.info("detect unresolved source " + href);
175             }
176             unresolvedCache.add(href);
177         } else {
178             // find a new cacheable source, add it in cache
179             if (verbose) {
180                 log.info("detect cacheable  source " + href);
181             }
182             sourceCache.put(href, source);
183         }
184 
185 //        if (href.matches("[a-zA-Z]+://.+")) {
186 //            String filename = null;
187 //            int beginIndex = href.lastIndexOf('/');
188 //            if (beginIndex > -1) {
189 //                filename = href.substring(beginIndex + 1);
190 //            }
191 //            if (filename != null && !filename.isEmpty()) {
192 //                source = findSource(filename, true);
193 //            }
194 //        } else {
195 //            source = findSource(href, false);
196 //        }        
197         return source;
198     }
199 
200     public void setVerbose(boolean verbose) {
201         this.verbose = verbose;
202     }
203 
204     public void setCl(ClassLoader cl) {
205         this.cl = cl;
206     }
207 
208     protected Source findHrefSource(String path) {
209         long t0 = System.nanoTime();
210         String filename;
211         int beginIndex = path.lastIndexOf('/');
212         if (beginIndex > -1) {
213             filename = path.substring(beginIndex + 1);
214         } else {
215             filename = path;
216         }
217         if (filename == null || filename.isEmpty()) {
218             return null;
219         }
220         String resource;
221 
222         resource = ".*/" + filename;
223 
224         if (verbose) {
225             log.info("will discover " + resource);
226         }
227 
228         URL url = null;
229 
230         // use given classloader to work in maven
231         List<URL> urls = null;
232         URLClassLoader ucl = null;
233         if (cl == null) {
234             ClassLoader cl2 = getClass().getClassLoader();
235             if (cl2 instanceof URLClassLoader) {
236                 ucl = (URLClassLoader) cl2;
237             }
238         }
239         if (cl instanceof URLClassLoader) {
240             ucl = (URLClassLoader) cl;
241         }
242         try {
243             urls = Resource.getURLs(resource, ucl);
244         } catch (ResourceNotFoundException rnfe) {
245             // Nothing to do
246         }
247 
248         if (urls != null && !urls.isEmpty()) {
249             url = urls.get(0);
250         }
251 
252 
253         Source source = null;
254 
255         if (url != null) {
256             if (verbose) {
257                 log.info(url.toString());
258             }
259             source = new StreamSource(url.toString());
260         }
261         if (verbose) {
262             String time = StringUtil.convertTime(System.nanoTime() - t0);
263             log.info("resolved in " + time);
264         }
265         return source;
266     }
267 
268     protected Source findRelativeSource(String path) {
269         long t0 = System.nanoTime();
270         String filename = path;
271 //        int beginIndex = path.lastIndexOf('/');
272 //        if (beginIndex > -1) {
273 //            filename = path.substring(beginIndex + 1);
274 //        } else {
275 //            filename = path;
276 //        }
277 //        if (filename == null || filename.isEmpty()) {
278 //            return null;
279 //        }
280         String resource;
281         if (base != null) {
282             resource = base + "/" + filename;
283         } else {
284             resource = filename;
285         }
286 
287         if (verbose) {
288             log.info("will discover " + resource);
289         }
290 
291         URL url = Resource.getURLOrNull(resource);
292 
293         Source source = null;
294 
295         if (url != null) {
296             if (verbose) {
297                 log.info(url.toString());
298             }
299             source = new StreamSource(url.toString());
300         }
301         if (verbose) {
302             String time = StringUtil.convertTime(System.nanoTime() - t0);
303             log.info("resolved in " + time);
304         }
305         return source;
306     }
307 }