001: /*
002: * $Id: PackageResource.java 4898 2006-03-13 13:44:52 -0800 (Mon, 13 Mar 2006)
003: * joco01 $ $Revision: 464232 $ $Date: 2006-03-13 13:44:52 -0800 (Mon, 13 Mar
004: * 2006) $
005: *
006: * ==============================================================================
007: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
008: * use this file except in compliance with the License. You may obtain a copy of
009: * the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
015: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
016: * License for the specific language governing permissions and limitations under
017: * the License.
018: */
019: package wicket.markup.html;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.net.JarURLConnection;
024: import java.net.URI;
025: import java.net.URISyntaxException;
026: import java.net.URL;
027: import java.net.URLConnection;
028: import java.util.ArrayList;
029: import java.util.Enumeration;
030: import java.util.List;
031: import java.util.Locale;
032: import java.util.jar.JarEntry;
033: import java.util.jar.JarFile;
034: import java.util.regex.Pattern;
035:
036: import javax.servlet.http.HttpServletResponse;
037:
038: import org.apache.commons.logging.Log;
039: import org.apache.commons.logging.LogFactory;
040:
041: import wicket.AbortException;
042: import wicket.Application;
043: import wicket.RequestCycle;
044: import wicket.SharedResources;
045: import wicket.WicketRuntimeException;
046: import wicket.protocol.http.WebRequestCycle;
047: import wicket.protocol.http.servlet.AbortWithWebErrorCodeException;
048: import wicket.util.lang.PackageName;
049: import wicket.util.lang.Packages;
050: import wicket.util.resource.IResourceStream;
051: import wicket.util.string.Strings;
052:
053: /**
054: * Represents a localizable static resource.
055: * <p>
056: * Use like eg:
057: *
058: * <pre>
059: * PackageResource IMG_UNKNOWN = PackageResource.get(EditPage.class, "questionmark.gif");
060: * </pre>
061: *
062: * where the static resource references image 'questionmark.gif' from the the
063: * package that EditPage is in to get a package resource.
064: * </p>
065: *
066: * @author Jonathan Locke
067: * @author Eelco Hillenius
068: */
069: public class PackageResource extends WebResource {
070: /**
071: * Exception thrown when the creation of a package resource is not allowed.
072: */
073: public static final class PackageResourceBlockedException extends
074: WicketRuntimeException {
075: private static final long serialVersionUID = 1L;
076:
077: /**
078: * Construct.
079: *
080: * @param message
081: */
082: public PackageResourceBlockedException(String message) {
083: super (message);
084: }
085: }
086:
087: /**
088: * common extension pattern for css files; matches all files with extension
089: * 'css'.
090: *
091: * @deprecated Will be removed in 2.0; contribute resources one by one
092: * instead
093: */
094: public static final Pattern EXTENSION_CSS = Pattern
095: .compile(".*\\.css");
096:
097: /**
098: * common extension pattern for javascript files; matches all files with
099: * extension 'js'.
100: *
101: * @deprecated Will be removed in 2.0; contribute resources one by one
102: * instead
103: */
104: public static final Pattern EXTENSION_JS = Pattern
105: .compile(".*\\.js");
106:
107: /** log. */
108: private static final Log log = LogFactory
109: .getLog(PackageResource.class);
110:
111: private static final long serialVersionUID = 1L;
112:
113: /**
114: * Binds the resources that match the provided pattern to the given
115: * application object. Will create any resources if not already in the
116: * shared resources of the application object.
117: *
118: * @param application
119: * The application to bind to.
120: * @param scope
121: * The scope of the resource.
122: * @param pattern
123: * A regular expression to match against the contents of the
124: * package of the provided scope class (eg ".*\\.js"
125: * will add all the files with extension "js" from that
126: * package).
127: *
128: * @deprecated Since Wicket 1.2.1 this method is effectively a no-op.
129: * {@link PackageResource package resources} are automatically
130: * tried and bound as shared resources so that they don't have
131: * to be pre-registered anymore. Will be removed in 2.0
132: */
133: public static void bind(Application application, Class scope,
134: Pattern pattern) {
135: }
136:
137: /**
138: * Binds the resources that match the provided pattern to the given
139: * application object. Will create any resources if not already in the
140: * shared resources of the application object and does that recursively when
141: * the recurse parameter is true, or just for the scoped package if that
142: * parameter is false
143: *
144: * @param application
145: * The application to bind to.
146: * @param scope
147: * The scope of the resource.
148: * @param pattern
149: * A regular expression to match against the contents of the
150: * package of the provided scope class (eg ".*\\.js"
151: * will add all the files with extension "js" from that
152: * package).
153: * @param recurse
154: * Whether this method should recurse into sub packages
155: *
156: * @deprecated Since Wicket 1.2.1 this method is effectively a no-op.
157: * {@link PackageResource package resources} are automatically
158: * tried and bound as shared resources so that they don't have
159: * to be pre-registered anymore. Will be removed in 2.0
160: */
161: public static void bind(Application application, Class scope,
162: Pattern pattern, boolean recurse) {
163: }
164:
165: /**
166: * Binds a resource to the given application object. Will create the
167: * resource if not already in the shared resources of the application
168: * object.
169: *
170: * @param application
171: * The application to bind to.
172: * @param scope
173: * The scope of the resource.
174: * @param name
175: * The name of the resource (like "myfile.js")
176: * @throw IllegalArgumentException when the requested package resource was
177: * not found
178: */
179: public static void bind(Application application, Class scope,
180: String name) {
181: bind(application, scope, name, null, null);
182: }
183:
184: /**
185: * Binds a resource to the given application object. Will create the
186: * resource if not already in the shared resources of the application
187: * object.
188: *
189: * @param application
190: * The application to bind to.
191: * @param scope
192: * The scope of the resource.
193: * @param name
194: * The name of the resource (like "myfile.js")
195: * @param locale
196: * The locale of the resource.
197: * @throw IllegalArgumentException when the requested package resource was
198: * not found
199: */
200: public static void bind(Application application, Class scope,
201: String name, Locale locale) {
202: bind(application, scope, name, locale, null);
203: }
204:
205: /**
206: * Binds a resource to the given application object. Will create the
207: * resource if not already in the shared resources of the application
208: * object.
209: *
210: * @param application
211: * The application to bind to.
212: * @param scope
213: * The scope of the resource.
214: * @param name
215: * The name of the resource (like "myfile.js")
216: * @param locale
217: * The locale of the resource.
218: * @param style
219: * The style of the resource.
220: * @throw IllegalArgumentException when the requested package resource was
221: * not found
222: */
223: public static void bind(Application application, Class scope,
224: String name, Locale locale, String style) {
225: if (name == null) {
226: throw new IllegalArgumentException(
227: "argument name may not be null");
228: }
229:
230: // first check on a direct hit for efficiency
231: if (exists(scope, name, locale, style)) {
232: // we have got a hit, so we may safely assume the name
233: // argument is not a regular expression, and can thus
234: // just add the resource and return
235: get(scope, name, locale, style);
236: } else {
237: throw new IllegalArgumentException(
238: "no package resource was found for scope " + scope
239: + ", name " + name + ", locale " + locale
240: + ", style " + style);
241: }
242: }
243:
244: /**
245: * Gets whether a resource for a given set of criteria exists.
246: *
247: * @param scope
248: * This argument will be used to get the class loader for loading
249: * the package resource, and to determine what package it is in.
250: * Typically this is the class in which you call this method
251: * @param path
252: * The path to the resource
253: * @param locale
254: * The locale of the resource
255: * @param style
256: * The style of the resource (see {@link wicket.Session})
257: * @return true if a resource could be loaded, false otherwise
258: */
259: public static boolean exists(final Class scope, final String path,
260: final Locale locale, final String style) {
261: String absolutePath = Packages.absolutePath(scope, path);
262: return Application.get().getResourceSettings()
263: .getResourceStreamLocator().locate(scope, absolutePath,
264: style, locale, null) != null;
265: }
266:
267: /**
268: * Gets non-localized resources for a given set of criteria. Multiple
269: * resource can be loaded for the same criteria if they match the pattern.
270: * If no resources were found, this method returns null.
271: *
272: * @param scope
273: * This argument will be used to get the class loader for loading
274: * the package resource, and to determine what package it is in.
275: * Typically this is the calling class/ the class in which you
276: * call this method
277: * @param pattern
278: * Regexp pattern to match resources
279: * @return The resources, never null but may be empty
280: * @deprecated Will be removed in 2.0; contribute resources one by one
281: * instead
282: */
283: public static PackageResource[] get(Class scope, Pattern pattern) {
284: return get(scope, pattern, false);
285: }
286:
287: /**
288: * Gets non-localized resources for a given set of criteria. Multiple
289: * resource can be loaded for the same criteria if they match the pattern.
290: * If no resources were found, this method returns null.
291: *
292: * @param scope
293: * This argument will be used to get the class loader for loading
294: * the package resource, and to determine what package it is in.
295: * Typically this is the calling class/ the class in which you
296: * call this method
297: * @param pattern
298: * Regexp pattern to match resources
299: * @param recurse
300: * Whether this method should recurse into sub packages
301: * @return The resources, never null but may be empty
302: * @deprecated Will be removed in 2.0; contribute resources one by one
303: * instead
304: */
305: public static PackageResource[] get(Class scope, Pattern pattern,
306: boolean recurse) {
307: final List resources = new ArrayList();
308: String packageRef = Strings.replaceAll(
309: PackageName.forClass(scope).getName(), ".", "/")
310: .toString();
311: ClassLoader loader = scope.getClassLoader();
312: try {
313: // loop through the resources of the package
314: Enumeration packageResources = loader
315: .getResources(packageRef);
316: while (packageResources.hasMoreElements()) {
317: URL resource = (URL) packageResources.nextElement();
318: URLConnection connection = resource.openConnection();
319: if (connection instanceof JarURLConnection) {
320: JarFile jf = ((JarURLConnection) connection)
321: .getJarFile();
322: scanJarFile(scope, pattern, recurse, resources,
323: packageRef, jf);
324: } else {
325: String absolutePath = scope.getResource("")
326: .toExternalForm();
327: File basedir;
328: URI uri;
329: try {
330: uri = new URI(absolutePath);
331: } catch (URISyntaxException e) {
332: throw new RuntimeException(e);
333: }
334: try {
335: basedir = new File(uri);
336: } catch (IllegalArgumentException e) {
337: log.debug("Can't construct the uri as a file: "
338: + absolutePath);
339: // if this is throwen then the path is not really a
340: // file. but could be a zip.
341: String jarZipPart = uri.getSchemeSpecificPart();
342: // lowercased for testing if jar/zip, but leave the real
343: // filespec unchanged
344: String lowerJarZipPart = jarZipPart
345: .toLowerCase();
346: int index = lowerJarZipPart.indexOf(".zip");
347: if (index == -1)
348: index = lowerJarZipPart.indexOf(".jar");
349: if (index == -1)
350: throw e;
351:
352: String filename = jarZipPart.substring(0,
353: index + 4); // 4 =
354: // len of ".jar" or ".zip"
355: log.debug("trying the filename: " + filename
356: + " to load as a zip/jar.");
357: JarFile jarFile = new JarFile(filename, false);
358: scanJarFile(scope, pattern, recurse, resources,
359: packageRef, jarFile);
360: return (PackageResource[]) resources
361: .toArray(new PackageResource[resources
362: .size()]);
363: }
364: if (!basedir.isDirectory()) {
365: throw new IllegalStateException(
366: "unable to read resources from directory "
367: + basedir);
368: }
369: }
370: }
371: } catch (IOException e) {
372: throw new WicketRuntimeException(e);
373: }
374:
375: return (PackageResource[]) resources
376: .toArray(new PackageResource[resources.size()]);
377: }
378:
379: /**
380: * Gets a non-localized resource for a given set of criteria. Only one
381: * resource will be loaded for the same criteria.
382: *
383: * @param scope
384: * This argument will be used to get the class loader for loading
385: * the package resource, and to determine what package it is in.
386: * Typically this is the calling class/ the class in which you
387: * call this method
388: * @param path
389: * The path to the resource
390: * @return The resource
391: */
392: public static PackageResource get(final Class scope,
393: final String path) {
394: return get(scope, path, null, null);
395: }
396:
397: /**
398: * Gets the resource for a given set of criteria. Only one resource will be
399: * loaded for the same criteria.
400: *
401: * @param scope
402: * This argument will be used to get the class loader for loading
403: * the package resource, and to determine what package it is in.
404: * Typically this is the class in which you call this method
405: * @param path
406: * The path to the resource
407: * @param locale
408: * The locale of the resource
409: * @param style
410: * The style of the resource (see {@link wicket.Session})
411: * @return The resource
412: */
413: public static PackageResource get(final Class scope,
414: final String path, final Locale locale, final String style) {
415: final SharedResources sharedResources = Application.get()
416: .getSharedResources();
417: PackageResource resource = (PackageResource) sharedResources
418: .get(scope, path, locale, style, true);
419: if (resource == null) {
420: resource = new PackageResource(scope, path, locale, style);
421: sharedResources.add(scope, path, locale, style, resource);
422: }
423: return resource;
424: }
425:
426: /* removed in 2.0 */
427: private static void scanJarFile(Class scope, Pattern pattern,
428: boolean recurse, final List resources, String packageRef,
429: JarFile jf) {
430: Enumeration enumeration = jf.entries();
431: while (enumeration.hasMoreElements()) {
432: JarEntry je = (JarEntry) enumeration.nextElement();
433: String name = je.getName();
434: if (name.startsWith(packageRef)) {
435: name = name.substring(packageRef.length() + 1);
436: if (pattern.matcher(name).matches()
437: && (recurse || (name.indexOf('/') == -1))) {
438: // we add the entry as a package resource
439: resources.add(get(scope, name, null, null));
440: }
441: }
442: }
443: }
444:
445: /** The path to the resource */
446: private final String absolutePath;
447:
448: /** The resource's locale */
449: private Locale locale;
450:
451: /** The path this resource was created with. */
452: private final String path;
453:
454: /** The scoping class, used for class loading and to determine the package. */
455: private final Class scope;
456:
457: /** The resource's style */
458: private final String style;
459:
460: /**
461: * Hidden constructor.
462: *
463: * @param scope
464: * This argument will be used to get the class loader for loading
465: * the package resource, and to determine what package it is in
466: * @param path
467: * The path to the resource
468: * @param locale
469: * The locale of the resource
470: * @param style
471: * The style of the resource
472: */
473: protected PackageResource(final Class scope, final String path,
474: final Locale locale, final String style) {
475: // Convert resource path to absolute path relative to base package
476: this .absolutePath = Packages.absolutePath(scope, path);
477:
478: IPackageResourceGuard guard = Application.get()
479: .getResourceSettings().getPackageResourceGuard();
480: if (!guard.accept(scope, path)) {
481: throw new PackageResourceBlockedException(
482: "package resource " + absolutePath
483: + " may not be accessed");
484: }
485:
486: this .scope = scope;
487: this .path = path;
488: this .locale = locale;
489: this .style = style;
490:
491: if (locale != null) {
492: // Get the resource stream so that the real locale that could be
493: // resolved is set.
494: getResourceStream();
495:
496: // Invalidate it again so that it won't hold up resources
497: invalidate();
498: }
499: }
500:
501: /**
502: * Gets the absolute path of the resource.
503: *
504: * @return the absolute resource path
505: */
506: public final String getAbsolutePath() {
507: return absolutePath;
508: }
509:
510: /**
511: * Gets the locale.
512: *
513: * @return The Locale of this package resource
514: */
515: public final Locale getLocale() {
516: return locale;
517: }
518:
519: /**
520: * Gets the path this resource was created with.
521: *
522: * @return the path
523: */
524: public final String getPath() {
525: return path;
526: }
527:
528: /**
529: * @return Gets the resource for the component.
530: */
531: public IResourceStream getResourceStream() {
532: // Locate resource
533: IResourceStream resourceStream = Application.get()
534: .getResourceSettings().getResourceStreamLocator()
535: .locate(scope, absolutePath, style, locale, null);
536:
537: // Check that resource was found
538: if (resourceStream == null) {
539: String msg = "Unable to find package resource [path = "
540: + absolutePath + ", style = " + style
541: + ", locale = " + locale + "]";
542: log.warn(msg);
543: if (RequestCycle.get() instanceof WebRequestCycle) {
544: throw new AbortWithWebErrorCodeException(
545: HttpServletResponse.SC_NOT_FOUND, msg);
546: } else {
547: throw new AbortException();
548: }
549: }
550: this .locale = resourceStream.getLocale();
551: return resourceStream;
552: }
553:
554: /**
555: * Gets the scoping class, used for class loading and to determine the
556: * package.
557: *
558: * @return the scoping class
559: */
560: public final Class getScope() {
561: return scope;
562: }
563:
564: /**
565: * Gets the style.
566: *
567: * @return the style
568: */
569: public final String getStyle() {
570: return style;
571: }
572: }
|