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 }