001: /*****************************************************************************
002: * Java Plug-in Framework (JPF)
003: * Copyright (C) 2004-2007 Dmitry Olshansky
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: *****************************************************************************/package org.java.plugin.standard;
019:
020: import java.io.BufferedInputStream;
021: import java.io.BufferedOutputStream;
022: import java.io.File;
023: import java.io.FileFilter;
024: import java.io.FileInputStream;
025: import java.io.FileOutputStream;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.OutputStream;
029: import java.net.JarURLConnection;
030: import java.net.MalformedURLException;
031: import java.net.URL;
032: import java.net.URLConnection;
033: import java.text.DateFormat;
034: import java.text.ParseException;
035: import java.text.SimpleDateFormat;
036: import java.util.Calendar;
037: import java.util.Collection;
038: import java.util.Date;
039: import java.util.Enumeration;
040: import java.util.HashMap;
041: import java.util.HashSet;
042: import java.util.LinkedList;
043: import java.util.Locale;
044: import java.util.Map;
045: import java.util.Properties;
046: import java.util.Set;
047: import java.util.StringTokenizer;
048: import java.util.regex.Pattern;
049: import java.util.zip.ZipEntry;
050: import java.util.zip.ZipFile;
051: import java.util.zip.ZipInputStream;
052:
053: import org.apache.commons.logging.Log;
054: import org.apache.commons.logging.LogFactory;
055: import org.java.plugin.registry.Identity;
056: import org.java.plugin.registry.Library;
057: import org.java.plugin.registry.PluginAttribute;
058: import org.java.plugin.registry.PluginDescriptor;
059: import org.java.plugin.registry.PluginElement;
060: import org.java.plugin.registry.PluginFragment;
061: import org.java.plugin.registry.UniqueIdentity;
062: import org.java.plugin.util.ExtendedProperties;
063: import org.java.plugin.util.IoUtil;
064:
065: /**
066: * This implementation of path resolver makes "shadow copy" of plug-in resources
067: * before resolving paths to them, this helps avoid locking of local resources
068: * and run native code from remote locations.
069: * <p>
070: * <b>Configuration parameters</b>
071: * </p>
072: * <p>
073: * This path resolver implementation supports following configuration
074: * parameters:
075: * <dl>
076: * <dt>shadowFolder</dt>
077: * <dd>Path to the folder where to copy resources to prevent their locking. By
078: * default this will be
079: * <code>System.getProperty("java.io.tmpdir") + "/.jpf-shadow"</code>.
080: * Please note that this folder will be maintained automatically by the
081: * Framework and might be cleared without any confirmation or notification.
082: * So it is strongly not recommended to use plug-ins folder (or other
083: * sensitive application directory) as shadow folder, this may lead to
084: * losing your data.</dd>
085: * <dt>unpackMode</dt>
086: * <dd>If <code>always</code>, "JAR'ed" or "ZIP'ed" plug-ins will be
087: * un-compressed to the shadow folder, if <code>never</code>, they will be
088: * just copied, if <code>smart</code>, the processing depends on plug-in
089: * content - if plug-in contains JAR libraries, it will be un-packed,
090: * otherwise just copied to shadow folder. It is also possible to add
091: * boolean "unpack" attribute to plug-in manifest, in this case, it's value
092: * will be taken into account. The default parameter value is
093: * <code>smart</code>.</dd>
094: * </dl>
095: * </p>
096: *
097: * @version $Id: ShadingPathResolver.java,v 1.5 2007/05/13 16:31:48 ddimon Exp $
098: */
099: public class ShadingPathResolver extends StandardPathResolver {
100: private static final String UNPACK_MODE_ALWAIS = "always"; //$NON-NLS-1$
101: private static final String UNPACK_MODE_NEVER = "never"; //$NON-NLS-1$
102: private static final String UNPACK_MODE_SMART = "smart"; //$NON-NLS-1$
103:
104: private File shadowFolder;
105: private String unpackMode;
106: private Map<String, URL> shadowUrlMap = new HashMap<String, URL>(); // <pluginId or fragmentId, shadow URL>
107: private Map<String, Boolean> unpackModeMap = new HashMap<String, Boolean>(); // <pluginId or fragmentId, Boolean>
108: private ShadowDataController controller;
109:
110: /**
111: * @see org.java.plugin.PathResolver#configure(ExtendedProperties)
112: */
113: @Override
114: public synchronized void configure(final ExtendedProperties config)
115: throws Exception {
116: super .configure(config);
117: String folder = config.getProperty("shadowFolder"); //$NON-NLS-1$
118: if ((folder != null) && (folder.length() > 0)) {
119: try {
120: shadowFolder = new File(folder).getCanonicalFile();
121: } catch (IOException ioe) {
122: log.warn("failed initializing shadow folder " + folder //$NON-NLS-1$
123: + ", falling back to the default folder", ioe); //$NON-NLS-1$
124: }
125: }
126: if (shadowFolder == null) {
127: shadowFolder = new File(System
128: .getProperty("java.io.tmpdir"), //$NON-NLS-1$
129: ".jpf-shadow"); //$NON-NLS-1$
130: }
131: log.debug("shadow folder is " + shadowFolder); //$NON-NLS-1$
132: if (!shadowFolder.exists()) {
133: shadowFolder.mkdirs();
134: }
135: unpackMode = config
136: .getProperty("unpackMode", UNPACK_MODE_SMART); //$NON-NLS-1$
137: log.debug("unpack mode parameter value is " + unpackMode); //$NON-NLS-1$
138: controller = ShadowDataController.init(shadowFolder,
139: buildFileFilter(config));
140: log.info("configured, shadow folder is " + shadowFolder); //$NON-NLS-1$
141: }
142:
143: private FileFilter buildFileFilter(final ExtendedProperties config) {
144: final FileFilter includesFilter;
145: String patterns = config.getProperty("includes"); //$NON-NLS-1$
146: if ((patterns != null) && (patterns.trim().length() > 0)) {
147: includesFilter = new RegexpFileFilter(patterns);
148: } else {
149: includesFilter = null;
150: }
151: final FileFilter excludesFilter;
152: patterns = config.getProperty("excludes"); //$NON-NLS-1$
153: if ((patterns != null) && (patterns.trim().length() > 0)) {
154: excludesFilter = new RegexpFileFilter(patterns);
155: } else {
156: excludesFilter = null;
157: }
158: if ((excludesFilter == null) && (includesFilter == null)) {
159: return null;
160: }
161: return new CombinedFileFilter(includesFilter, excludesFilter);
162: }
163:
164: /**
165: * @see org.java.plugin.standard.StandardPathResolver#registerContext(
166: * org.java.plugin.registry.Identity, java.net.URL)
167: */
168: @Override
169: public void registerContext(Identity idt, URL url) {
170: super .registerContext(idt, url);
171: Boolean mode;
172: if (UNPACK_MODE_ALWAIS.equalsIgnoreCase(unpackMode)) {
173: mode = Boolean.TRUE;
174: } else if (UNPACK_MODE_NEVER.equalsIgnoreCase(unpackMode)) {
175: mode = Boolean.FALSE;
176: } else {
177: PluginDescriptor descr = null;
178: PluginFragment fragment = null;
179: if (idt instanceof PluginDescriptor) {
180: descr = (PluginDescriptor) idt;
181: } else if (idt instanceof PluginFragment) {
182: fragment = (PluginFragment) idt;
183: descr = fragment.getRegistry().getPluginDescriptor(
184: fragment.getPluginId());
185: } else if (idt instanceof PluginElement) {
186: PluginElement<?> element = (PluginElement) idt;
187: descr = element.getDeclaringPluginDescriptor();
188: fragment = element.getDeclaringPluginFragment();
189: } else {
190: throw new IllegalArgumentException(
191: "unknown identity class " //$NON-NLS-1$
192: + idt.getClass().getName());
193: }
194: mode = getUnpackMode(descr, fragment);
195: }
196: log.debug("unpack mode for " + idt + " is " + mode); //$NON-NLS-1$ //$NON-NLS-2$
197: unpackModeMap.put(idt.getId(), mode);
198: }
199:
200: private Boolean getUnpackMode(final PluginDescriptor descr,
201: final PluginFragment fragment) {
202: for (PluginAttribute attr : filterCollection(descr
203: .getAttributes("unpack"), fragment)) { //$NON-NLS-1$
204: return Boolean.valueOf("false".equalsIgnoreCase( //$NON-NLS-1$
205: attr.getValue()));
206: }
207: for (Library lib : filterCollection(descr.getLibraries(),
208: fragment)) {
209: if (lib.isCodeLibrary()
210: && (lib.getPath().toLowerCase(Locale.getDefault())
211: .endsWith(".jar") //$NON-NLS-1$
212: || lib.getPath().toLowerCase(Locale.getDefault())
213: .endsWith(".zip"))) { //$NON-NLS-1$
214: return Boolean.TRUE;
215: }
216: }
217: return Boolean.FALSE;
218: }
219:
220: private <T extends PluginElement<?>> Collection<T> filterCollection(
221: final Collection<T> coll, final PluginFragment fragment) {
222: if (fragment == null) {
223: return coll;
224: }
225: LinkedList<T> result = new LinkedList<T>();
226: for (T element : coll) {
227: if (fragment.equals(element.getDeclaringPluginFragment())) {
228: result.add(element);
229: }
230: }
231: return result;
232: }
233:
234: /**
235: * @see org.java.plugin.standard.StandardPathResolver#unregisterContext(
236: * java.lang.String)
237: */
238: @Override
239: public void unregisterContext(String id) {
240: shadowUrlMap.remove(id);
241: unpackModeMap.remove(id);
242: super .unregisterContext(id);
243: }
244:
245: /**
246: * @see org.java.plugin.PathResolver#resolvePath(
247: * org.java.plugin.registry.Identity, java.lang.String)
248: */
249: @Override
250: public URL resolvePath(final Identity idt, final String path) {
251: URL baseUrl;
252: if (idt instanceof PluginDescriptor) {
253: baseUrl = getBaseUrl((PluginDescriptor) idt);
254: } else if (idt instanceof PluginFragment) {
255: baseUrl = getBaseUrl((PluginFragment) idt);
256: } else if (idt instanceof PluginElement) {
257: PluginElement<?> element = (PluginElement) idt;
258: if (element.getDeclaringPluginFragment() != null) {
259: baseUrl = getBaseUrl(element
260: .getDeclaringPluginFragment());
261: } else {
262: baseUrl = getBaseUrl(element
263: .getDeclaringPluginDescriptor());
264: }
265: } else {
266: throw new IllegalArgumentException(
267: "unknown identity class " //$NON-NLS-1$
268: + idt.getClass().getName());
269: }
270: return resolvePath(baseUrl, path);
271: }
272:
273: protected synchronized URL getBaseUrl(final UniqueIdentity uid) {
274: URL result = shadowUrlMap.get(uid.getId());
275: if (result != null) {
276: return result;
277: }
278: result = controller.shadowResource(getRegisteredContext(uid
279: .getId()), uid.getUniqueId(), (unpackModeMap.get(uid
280: .getId())).booleanValue());
281: shadowUrlMap.put(uid.getId(), result);
282: return result;
283: }
284: }
285:
286: final class ShadingUtil {
287: static String getExtension(final String name) {
288: if ((name == null) || (name.length() == 0)) {
289: return null;
290: }
291: int p = name.lastIndexOf('.');
292: if ((p != -1) && (p > 0) && (p < name.length() - 1)) {
293: return name.substring(p + 1);
294: }
295: return null;
296: }
297:
298: static void unpack(final ZipFile zipFile, final File destFolder)
299: throws IOException {
300: for (Enumeration<? extends ZipEntry> en = zipFile.entries(); en
301: .hasMoreElements();) {
302: ZipEntry entry = en.nextElement();
303: String name = entry.getName();
304: File entryFile = new File(destFolder.getCanonicalPath()
305: + "/" + name); //$NON-NLS-1$
306: if (name.endsWith("/")) { //$NON-NLS-1$
307: if (!entryFile.exists() && !entryFile.mkdirs()) {
308: throw new IOException(
309: "can't create folder " + entryFile); //$NON-NLS-1$
310: }
311: } else {
312: File folder = entryFile.getParentFile();
313: if (!folder.exists() && !folder.mkdirs()) {
314: throw new IOException(
315: "can't create folder " + folder); //$NON-NLS-1$
316: }
317: OutputStream out = new BufferedOutputStream(
318: new FileOutputStream(entryFile, false));
319: try {
320: InputStream in = zipFile.getInputStream(entry);
321: try {
322: IoUtil.copyStream(in, out, 1024);
323: } finally {
324: in.close();
325: }
326: } finally {
327: out.close();
328: }
329: }
330: entryFile.setLastModified(entry.getTime());
331: }
332: }
333:
334: static void unpack(final InputStream strm, final File destFolder)
335: throws IOException {
336: ZipInputStream zipStrm = new ZipInputStream(strm);
337: ZipEntry entry = zipStrm.getNextEntry();
338: while (entry != null) {
339: String name = entry.getName();
340: File entryFile = new File(destFolder.getCanonicalPath()
341: + "/" + name); //$NON-NLS-1$
342: if (name.endsWith("/")) { //$NON-NLS-1$
343: if (!entryFile.exists() && !entryFile.mkdirs()) {
344: throw new IOException(
345: "can't create folder " + entryFile); //$NON-NLS-1$
346: }
347: } else {
348: File folder = entryFile.getParentFile();
349: if (!folder.exists() && !folder.mkdirs()) {
350: throw new IOException(
351: "can't create folder " + folder); //$NON-NLS-1$
352: }
353: OutputStream out = new BufferedOutputStream(
354: new FileOutputStream(entryFile, false));
355: try {
356: IoUtil.copyStream(zipStrm, out, 1024);
357: } finally {
358: out.close();
359: }
360: }
361: entryFile.setLastModified(entry.getTime());
362: entry = zipStrm.getNextEntry();
363: }
364: }
365:
366: static boolean deleteFile(final File file) {
367: if (file.isDirectory()) {
368: IoUtil.emptyFolder(file);
369: }
370: return file.delete();
371: }
372:
373: static Date getLastModified(final URL url) throws IOException {
374: long result = 0;
375: if ("jar".equalsIgnoreCase(url.getProtocol())) { //$NON-NLS-1$
376: String urlStr = url.toExternalForm();
377: int p = urlStr.indexOf("!/"); //$NON-NLS-1$
378: if (p != -1) {
379: //sourceFile = IoUtil.url2file(new URL(urlStr.substring(4, p)));
380: return getLastModified(new URL(urlStr.substring(4, p)));
381: }
382: }
383: File sourceFile = IoUtil.url2file(url);
384: if (sourceFile != null) {
385: result = sourceFile.lastModified();
386: } else {
387: URLConnection cnn = url.openConnection();
388: try {
389: cnn.setUseCaches(false);
390: cnn.setDoInput(false); // this should force using HTTP HEAD method
391: result = cnn.getLastModified();
392: } finally {
393: try {
394: cnn.getInputStream().close();
395: } catch (IOException ioe) {
396: // ignore
397: }
398: }
399: }
400: if (result == 0) {
401: throw new IOException(
402: "can't retrieve modification date for resource " //$NON-NLS-1$
403: + url);
404: }
405: // for some reason modification milliseconds for some files are unstable
406: Calendar cldr = Calendar.getInstance(Locale.ENGLISH);
407: cldr.setTime(new Date(result));
408: cldr.set(Calendar.MILLISECOND, 0);
409: return cldr.getTime();
410: }
411:
412: private static String getRelativePath(final File base,
413: final File file) throws IOException {
414: String basePath;
415: String filePath = file.getCanonicalPath();
416: if (base.isFile()) {
417: File baseParent = base.getParentFile();
418: if (baseParent == null) {
419: return null;
420: }
421: basePath = baseParent.getCanonicalPath();
422: } else {
423: basePath = base.getCanonicalPath();
424: }
425: if (!basePath.endsWith(File.separator)) {
426: basePath += File.separator;
427: }
428: int p = basePath.indexOf(File.separatorChar);
429: String prefix = null;
430: while (p != -1) {
431: String newPrefix = basePath.substring(0, p + 1);
432: if (!filePath.startsWith(newPrefix)) {
433: break;
434: }
435: prefix = newPrefix;
436: p = basePath.indexOf(File.separatorChar, p + 1);
437: }
438: if (prefix == null) {
439: return null;
440: }
441: filePath = filePath.substring(prefix.length());
442: if (prefix.length() == basePath.length()) {
443: return filePath;
444: }
445: int c = 0;
446: p = basePath.indexOf(File.separatorChar, prefix.length());
447: while (p != -1) {
448: c++;
449: p = basePath.indexOf(File.separatorChar, p + 1);
450: }
451: for (int i = 0; i < c; i++) {
452: filePath = ".." + File.separator + filePath; //$NON-NLS-1$
453: }
454: return filePath;
455: }
456:
457: private static String getRelativeUrl(final File base,
458: final File file) throws IOException {
459: String result = ShadingUtil.getRelativePath(base, file);
460: if (result == null) {
461: return null;
462: }
463: result = result.replace('\\', '/');
464: if (file.isDirectory() && !result.endsWith("/")) { //$NON-NLS-1$
465: result += "/"; //$NON-NLS-1$
466: }
467: return result;
468: }
469:
470: static String getRelativeUrl(final File base, final URL url)
471: throws IOException {
472: File file = IoUtil.url2file(url);
473: if (file != null) {
474: String result = getRelativeUrl(base, file);
475: if (result != null) {
476: return result;
477: }
478: }
479: if ("jar".equalsIgnoreCase(url.getProtocol())) { //$NON-NLS-1$
480: String urlStr = url.toExternalForm();
481: int p = urlStr.indexOf("!/"); //$NON-NLS-1$
482: if (p != -1) {
483: return "jar:" //$NON-NLS-1$
484: + getRelativeUrl(base, new URL(urlStr
485: .substring(4, p)))
486: + urlStr.substring(p);
487: }
488: }
489: return url.toExternalForm();
490: }
491:
492: static URL buildURL(final URL base, final String url)
493: throws MalformedURLException {
494: if (!url.toLowerCase(Locale.ENGLISH).startsWith("jar:")) { //$NON-NLS-1$
495: return new URL(base, url);
496: }
497: int p = url.indexOf("!/"); //$NON-NLS-1$
498: if (p == -1) {
499: return new URL(base, url);
500: }
501: return new URL("jar:" //$NON-NLS-1$
502: + new URL(base, url.substring(4, p)).toExternalForm()
503: + url.substring(p));
504: }
505:
506: private ShadingUtil() {
507: // no-op
508: }
509: }
510:
511: final class ShadowDataController {
512: private static final String META_FILE_NAME = ".meta"; //$NON-NLS-1$
513:
514: private final Log log = LogFactory
515: .getLog(ShadowDataController.class);
516: private final File shadowFolder;
517: private final URL shadowFolderUrl;
518: private final Properties metaData;
519: private final DateFormat dtf = new SimpleDateFormat(
520: "yyyy-MM-dd HH:mm:ss"); //$NON-NLS-1$
521: private final FileFilter fileFilter;
522:
523: static ShadowDataController init(final File shadowFolder,
524: final FileFilter filter) throws IOException {
525: ShadowDataController result = new ShadowDataController(
526: shadowFolder, filter);
527: result.quickCheck();
528: result.save();
529: return result;
530: }
531:
532: private ShadowDataController(final File folder,
533: final FileFilter filter) throws IOException {
534: shadowFolder = folder;
535: fileFilter = filter;
536: shadowFolderUrl = IoUtil.file2url(folder);
537: File metaFile = new File(shadowFolder, META_FILE_NAME);
538: metaData = new Properties();
539: if (metaFile.isFile()) {
540: try {
541: InputStream in = new FileInputStream(metaFile);
542: try {
543: metaData.load(in);
544: } finally {
545: in.close();
546: }
547: if (log.isDebugEnabled()) {
548: log.debug("meta-data loaded from file " + metaFile); //$NON-NLS-1$
549: }
550: } catch (IOException ioe) {
551: log
552: .warn(
553: "failed loading meta-data from file " + metaFile, ioe); //$NON-NLS-1$
554: }
555: }
556: }
557:
558: private void save() {
559: File metaFile = new File(shadowFolder, META_FILE_NAME);
560: try {
561: OutputStream out = new FileOutputStream(metaFile, false);
562: try {
563: metaData.store(out,
564: "This is automatically generated file."); //$NON-NLS-1$
565: } finally {
566: out.close();
567: }
568: if (log.isDebugEnabled()) {
569: log.debug("meta-data saved to file " + metaFile); //$NON-NLS-1$
570: }
571: } catch (IOException ioe) {
572: log
573: .warn(
574: "failed saving meta-data to file " + metaFile, ioe); //$NON-NLS-1$
575: }
576: }
577:
578: private void quickCheck() {
579: File[] files = shadowFolder.listFiles(new ShadowFileFilter());
580: for (File file : files) {
581: if (metaData.containsValue(file.getName())) {
582: continue;
583: }
584: if (ShadingUtil.deleteFile(file)) {
585: if (log.isDebugEnabled()) {
586: log.debug("deleted shadow file " + file); //$NON-NLS-1$
587: }
588: } else {
589: log.warn("can't delete shadow file " + file); //$NON-NLS-1$
590: }
591: }
592: Set<Object> uids = new HashSet<Object>();
593: for (Map.Entry<Object, Object> entry : metaData.entrySet()) {
594: String key = (String) entry.getKey();
595: if (!key.startsWith("uid:")) { //$NON-NLS-1$
596: continue;
597: }
598: uids.add(entry.getValue());
599: }
600: for (Object object : uids) {
601: quickCheck((String) object);
602: }
603: }
604:
605: private void quickCheck(final String uid) {
606: if (log.isDebugEnabled()) {
607: log.debug("quick check of UID " + uid); //$NON-NLS-1$
608: }
609: String url = metaData.getProperty("source:" + uid, null); //$NON-NLS-1$
610: String file = metaData.getProperty("file:" + uid, null); //$NON-NLS-1$
611: String modified = metaData.getProperty("modified:" + uid, null); //$NON-NLS-1$
612: if ((url == null) || (file == null) || (modified == null)) {
613: if (log.isDebugEnabled()) {
614: log.debug("meta-data incomplete, UID=" + uid); //$NON-NLS-1$
615: }
616: remove(uid);
617: return;
618: }
619: try {
620: if (!dtf.parse(modified).equals(
621: ShadingUtil.getLastModified(ShadingUtil.buildURL(
622: shadowFolderUrl, url)))) {
623: if (log.isDebugEnabled()) {
624: log
625: .debug("source modification detected, UID=" + uid //$NON-NLS-1$
626: + ", source=" + url); //$NON-NLS-1$
627: }
628: remove(uid);
629: }
630: } catch (IOException ioe) {
631: log.warn("quick check failed", ioe); //$NON-NLS-1$
632: remove(uid);
633: } catch (ParseException pe) {
634: log.warn("quick check failed", pe); //$NON-NLS-1$
635: remove(uid);
636: }
637: }
638:
639: private void remove(final String uid) {
640: String file = metaData.getProperty("file:" + uid, null); //$NON-NLS-1$
641: if (file != null) {
642: File lostFile = new File(shadowFolder, file);
643: if (ShadingUtil.deleteFile(lostFile)) {
644: if (log.isDebugEnabled()) {
645: log.debug("deleted lost file " + file); //$NON-NLS-1$
646: }
647: } else {
648: log.warn("can't delete lost file " + file); //$NON-NLS-1$
649: }
650: }
651: boolean removed = metaData.remove("uid:" + uid) != null; //$NON-NLS-1$
652: removed |= metaData.remove("source:" + uid) != null; //$NON-NLS-1$
653: removed |= metaData.remove("file:" + uid) != null; //$NON-NLS-1$
654: removed |= metaData.remove("modified:" + uid) != null; //$NON-NLS-1$
655: if (removed && log.isDebugEnabled()) {
656: log.debug("removed meta-data, UID=" + uid); //$NON-NLS-1$
657: }
658: }
659:
660: private URL add(final String uid, final URL sourceUrl,
661: final File file, final Date modified) throws IOException {
662: URL result = IoUtil.file2url(file);
663: metaData.setProperty("uid:" + uid, uid); //$NON-NLS-1$
664: String source = ShadingUtil.getRelativeUrl(shadowFolder,
665: sourceUrl);
666: metaData.setProperty("source:" + uid, source); //$NON-NLS-1$
667: metaData.setProperty("file:" + uid, file.getName()); //$NON-NLS-1$
668: metaData.setProperty("modified:" + uid, dtf.format(modified)); //$NON-NLS-1$
669: save();
670: if (log.isDebugEnabled()) {
671: log.debug("shading done, UID=" + uid + ", source=" //$NON-NLS-1$ //$NON-NLS-2$
672: + source + ", file=" + result //$NON-NLS-1$
673: + ", modified=" + dtf.format(modified)); //$NON-NLS-1$
674: }
675: return result;
676: }
677:
678: URL shadowResource(final URL source, final String uid,
679: final boolean unpack) {
680: try {
681: URL result = deepCheck(source, uid);
682: if (result != null) {
683: if (log.isDebugEnabled()) {
684: log.debug("got actual shaded resource, UID=" + uid //$NON-NLS-1$
685: + ", source=" + source //$NON-NLS-1$
686: + ", file=" + result); //$NON-NLS-1$
687: }
688: return result;
689: }
690: } catch (Exception e) {
691: log.warn("deep check failed, UID=" + uid //$NON-NLS-1$
692: + ", URL=" + source, e); //$NON-NLS-1$
693: remove(uid);
694: }
695: Date lastModified;
696: try {
697: lastModified = ShadingUtil.getLastModified(source);
698: } catch (IOException ioe) {
699: log.error(
700: "shading failed, can't get modification date for " //$NON-NLS-1$
701: + source, ioe);
702: return source;
703: }
704: File file = IoUtil.url2file(source);
705: if ((file != null) && file.isDirectory()) {
706: // copy local folder to the shadow directory
707: try {
708: File rootFolder = new File(shadowFolder, uid);
709: IoUtil.copyFolder(file, rootFolder, true, true,
710: fileFilter);
711: return add(uid, source, rootFolder, lastModified);
712: } catch (IOException ioe) {
713: log.error("failed shading local folder " + file, ioe); //$NON-NLS-1$
714: return source;
715: }
716: }
717: try {
718: if ("jar".equalsIgnoreCase(source.getProtocol())) { //$NON-NLS-1$
719: String urlStr = source.toExternalForm();
720: int p = urlStr.indexOf("!/"); //$NON-NLS-1$
721: if (p == -1) {
722: p = urlStr.length();
723: }
724: URL jarFileURL = new URL(urlStr.substring(4, p));
725: if (!unpack) {
726: String ext = ShadingUtil.getExtension(jarFileURL
727: .getFile());
728: if (ext == null) {
729: ext = "jar"; //$NON-NLS-1$
730: }
731: File shadowFile = new File(shadowFolder, uid + '.'
732: + ext);
733: File sourceFile = IoUtil.url2file(jarFileURL);
734: InputStream in;
735: if (sourceFile != null) {
736: in = new BufferedInputStream(
737: new FileInputStream(sourceFile));
738: } else {
739: in = jarFileURL.openStream();
740: }
741: try {
742: OutputStream out = new FileOutputStream(
743: shadowFile, false);
744: try {
745: IoUtil.copyStream(in, out, 1024);
746: } finally {
747: out.close();
748: }
749: } finally {
750: in.close();
751: }
752: return add(uid, source, shadowFile, lastModified);
753: }
754: URLConnection cnn = null;
755: try {
756: File sourceFile = IoUtil.url2file(jarFileURL);
757: ZipFile zipFile;
758: if (sourceFile != null) {
759: zipFile = new ZipFile(sourceFile);
760: } else {
761: cnn = source.openConnection();
762: cnn.setUseCaches(false);
763: zipFile = ((JarURLConnection) cnn).getJarFile();
764: }
765: File rootFolder = new File(shadowFolder, uid);
766: try {
767: ShadingUtil.unpack(zipFile, rootFolder);
768: } finally {
769: zipFile.close();
770: }
771: return add(uid, source, rootFolder, lastModified);
772: } finally {
773: if (cnn != null) {
774: cnn.getInputStream().close();
775: }
776: }
777: }
778: } catch (IOException ioe) {
779: log.error("failed shading URL connection " + source, ioe); //$NON-NLS-1$
780: return source;
781: }
782: String fileName = source.getFile();
783: if (fileName == null) {
784: log.warn("can't get file name from resource " + source //$NON-NLS-1$
785: + ", shading failed"); //$NON-NLS-1$
786: return source;
787: }
788: String ext = ShadingUtil.getExtension(fileName);
789: if (ext == null) {
790: log
791: .warn("can't get file name extension for resource " + source //$NON-NLS-1$
792: + ", shading failed"); //$NON-NLS-1$
793: return source;
794: }
795: if (unpack && ("jar".equalsIgnoreCase(ext) //$NON-NLS-1$
796: || "zip".equalsIgnoreCase(ext))) { //$NON-NLS-1$
797: try {
798: InputStream strm = source.openStream();
799: File rootFolder = new File(shadowFolder, uid);
800: try {
801: ShadingUtil.unpack(strm, rootFolder);
802: } finally {
803: strm.close();
804: }
805: return add(uid, source, rootFolder, lastModified);
806: } catch (IOException ioe) {
807: log
808: .error(
809: "failed shading packed resource " + source, ioe); //$NON-NLS-1$
810: return source;
811: }
812: }
813: try {
814: File shadowFile = new File(shadowFolder, uid + '.' + ext);
815: InputStream in = source.openStream();
816: try {
817: OutputStream out = new FileOutputStream(shadowFile,
818: false);
819: try {
820: IoUtil.copyStream(in, out, 1024);
821: } finally {
822: out.close();
823: }
824: } finally {
825: in.close();
826: }
827: return add(uid, source, shadowFile, lastModified);
828: } catch (IOException ioe) {
829: log.error("failed shading resource file " + source, ioe); //$NON-NLS-1$
830: return source;
831: }
832: }
833:
834: private URL deepCheck(final URL source, final String uid)
835: throws Exception {
836: String url = metaData.getProperty("source:" + uid, null); //$NON-NLS-1$
837: if (url == null) {
838: if (log.isDebugEnabled()) {
839: log.debug("URL not found in meta-data, UID=" + uid); //$NON-NLS-1$
840: }
841: remove(uid);
842: return null;
843: }
844: if (log.isDebugEnabled()) {
845: log.debug("URL found in meta-data, UID=" //$NON-NLS-1$
846: + uid + ", source=" + source //$NON-NLS-1$
847: + ", storedURL=" + url); //$NON-NLS-1$
848: }
849: URL storedSource = ShadingUtil.buildURL(shadowFolderUrl, url);
850: if (!storedSource.equals(source)) {
851: if (log.isDebugEnabled()) {
852: log.debug("inconsistent URL found in meta-data, UID=" //$NON-NLS-1$
853: + uid + ", source=" + source //$NON-NLS-1$
854: + ", storedSource=" + storedSource); //$NON-NLS-1$
855: }
856: remove(uid);
857: return null;
858: }
859: String modified = metaData.getProperty("modified:" + uid, null); //$NON-NLS-1$
860: if (modified == null) {
861: if (log.isDebugEnabled()) {
862: log
863: .debug("modification info not found in meta-data, UID=" //$NON-NLS-1$
864: + uid);
865: }
866: remove(uid);
867: return null;
868: }
869: if (!ShadingUtil.getLastModified(source).equals(
870: dtf.parse(modified))) {
871: if (log.isDebugEnabled()) {
872: log.debug("source modification detected, UID=" + uid //$NON-NLS-1$
873: + ", source=" + source); //$NON-NLS-1$
874: }
875: remove(uid);
876: return null;
877: }
878: String fileStr = metaData.getProperty("file:" + uid, null); //$NON-NLS-1$
879: if (fileStr == null) {
880: if (log.isDebugEnabled()) {
881: log
882: .debug("file info not found in meta-data, UID=" + uid); //$NON-NLS-1$
883: }
884: remove(uid);
885: return null;
886: }
887: File file = new File(shadowFolder, fileStr);
888: if (!file.exists()) {
889: if (log.isDebugEnabled()) {
890: log.debug("shadow file not found, UID=" + uid //$NON-NLS-1$
891: + ", source=" + source //$NON-NLS-1$
892: + ", file=" + file); //$NON-NLS-1$
893: }
894: remove(uid);
895: return null;
896: }
897: File sourceFile = IoUtil.url2file(source);
898: if ((sourceFile != null) && sourceFile.isDirectory()) {
899: IoUtil.synchronizeFolders(sourceFile, file, fileFilter);
900: if (log.isDebugEnabled()) {
901: log.debug("folders synchronized, UID=" + uid //$NON-NLS-1$
902: + ", srcFile=" + sourceFile //$NON-NLS-1$
903: + ", destFile=" + file); //$NON-NLS-1$
904: }
905: } else {
906: if (log.isDebugEnabled()) {
907: log
908: .debug("source " + source + " (file is " + sourceFile //$NON-NLS-1$ //$NON-NLS-2$
909: + ") is not local folder, " //$NON-NLS-1$
910: + "skipping synchronization, UID=" + uid); //$NON-NLS-1$
911: }
912: }
913: return IoUtil.file2url(file);
914: }
915:
916: static class ShadowFileFilter implements FileFilter {
917: /**
918: * @see java.io.FileFilter#accept(java.io.File)
919: */
920: public boolean accept(final File file) {
921: return !META_FILE_NAME.equals(file.getName());
922: }
923: }
924: }
925:
926: final class RegexpFileFilter implements FileFilter {
927: private final Pattern[] patterns;
928:
929: RegexpFileFilter(final String str) {
930: StringTokenizer st = new StringTokenizer(str, "|", false); //$NON-NLS-1$
931: patterns = new Pattern[st.countTokens()];
932: for (int i = 0; i < patterns.length; i++) {
933: String pattern = st.nextToken();
934: if ((pattern == null) || (pattern.trim().length() == 0)) {
935: continue;
936: }
937: patterns[i] = Pattern.compile(pattern.trim());
938: }
939: }
940:
941: /**
942: * @see java.io.FileFilter#accept(java.io.File)
943: */
944: public boolean accept(final File file) {
945: for (int i = 0; i < patterns.length; i++) {
946: if (patterns[i] == null) {
947: continue;
948: }
949: if (patterns[i].matcher(file.getName()).matches()) {
950: return true;
951: }
952: }
953: return false;
954: }
955: }
956:
957: final class CombinedFileFilter implements FileFilter {
958: private final FileFilter includesFilter;
959: private final FileFilter excludesFilter;
960:
961: CombinedFileFilter(final FileFilter includes,
962: final FileFilter excludes) {
963: includesFilter = includes;
964: excludesFilter = excludes;
965: }
966:
967: /**
968: * @see java.io.FileFilter#accept(java.io.File)
969: */
970: public boolean accept(final File file) {
971: if (includesFilter != null) {
972: if (includesFilter.accept(file)) {
973: return true;
974: }
975: }
976: if ((excludesFilter != null) && excludesFilter.accept(file)) {
977: return false;
978: }
979: return true;
980: }
981: }
|