001: /*
002: * Helma License Notice
003: *
004: * The contents of this file are subject to the Helma License
005: * Version 2.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://adele.helma.org/download/helma/license.txt
008: *
009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
010: *
011: * $RCSfile$
012: * $Author: root $
013: * $Revision: 8604 $
014: * $Date: 2007-09-28 15:16:38 +0200 (Fre, 28 Sep 2007) $
015: */
016:
017: package helma.framework.repository;
018:
019: import helma.util.StringUtils;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.util.*;
024: import java.util.zip.ZipEntry;
025: import java.util.zip.ZipFile;
026:
027: public final class ZipRepository extends AbstractRepository {
028:
029: // zip file serving sub-repositories and zip file resources
030: private File file;
031:
032: // the nested directory depth of this repository
033: private int depth;
034:
035: String entryPath;
036:
037: private long lastModified = -1;
038:
039: /**
040: * Constructs a ZipRespository using the given argument
041: * @param initArgs absolute path to the zip file
042: */
043: public ZipRepository(String initArgs) {
044: this (new File(initArgs), null, null);
045: }
046:
047: /**
048: * Constructs a ZipRespository using the given argument
049: * @param file zip file
050: */
051: public ZipRepository(File file) {
052: this (file, null, null);
053: }
054:
055: /**
056: * Constructs a ZipRepository using the given zip file as top-level
057: * repository
058: * @param file a zip file
059: * @param parent the parent repository, or null
060: */
061: public ZipRepository(File file, Repository parent) {
062: this (file, parent, null);
063: }
064:
065: /**
066: * Constructs a ZipRepository using the zip entryName belonging to the given
067: * zip file and top-level repository
068: * @param file a zip file
069: * @param zipentry zip entryName
070: * @param parent repository
071: */
072: private ZipRepository(File file, Repository parent,
073: ZipEntry zipentry) {
074: // make sure our file has an absolute path,
075: // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4117557
076: if (file.isAbsolute()) {
077: this .file = file;
078: } else {
079: this .file = file.getAbsoluteFile();
080: }
081: this .parent = parent;
082:
083: if (zipentry == null) {
084: name = shortName = file.getName();
085: depth = 0;
086: entryPath = "";
087: } else {
088: String[] pathArray = StringUtils.split(zipentry.getName(),
089: "/");
090: depth = pathArray.length;
091: shortName = pathArray[depth - 1];
092: entryPath = zipentry.getName();
093: name = new StringBuffer(parent.getName()).append('/')
094: .append(shortName).toString();
095: }
096: }
097:
098: /**
099: * Returns a java.util.zip.ZipFile for this repository. It is the caller's
100: * responsability to call close() in it when it is no longer needed.
101: * @return a ZipFile for reading
102: * @throws IOException
103: */
104: protected ZipFile getZipFile() throws IOException {
105: return new ZipFile(file);
106: }
107:
108: public synchronized void update() {
109: if (file.lastModified() != lastModified || repositories == null
110: || resources == null) {
111: lastModified = file.lastModified();
112: ZipFile zipfile = null;
113:
114: try {
115: zipfile = getZipFile();
116: Enumeration en = zipfile.entries();
117: HashMap newRepositories = new HashMap();
118: HashMap newResources = new HashMap();
119:
120: while (en.hasMoreElements()) {
121: ZipEntry entry = (ZipEntry) en.nextElement();
122: String eName = entry.getName();
123:
124: if (!eName.regionMatches(0, entryPath, 0, entryPath
125: .length())) {
126: // names don't match - not a child of ours
127: continue;
128: }
129: String[] entrypath = StringUtils.split(eName, "/");
130: if (depth > 0
131: && !shortName.equals(entrypath[depth - 1])) {
132: // catch case where our name is Foo and other's is FooBar
133: continue;
134: }
135:
136: // create new repositories and resources for all entries with a
137: // path depth of this.depth + 1
138: if (entrypath.length == depth + 1
139: && !entry.isDirectory()) {
140: // create a new child resource
141: ZipResource resource = new ZipResource(entry
142: .getName(), this );
143: newResources.put(resource.getShortName(),
144: resource);
145: } else if (entrypath.length > depth) {
146: // create a new child repository
147: if (!newRepositories
148: .containsKey(entrypath[depth])) {
149: ZipEntry child = composeChildEntry(entrypath[depth]);
150: ZipRepository rep = new ZipRepository(file,
151: this , child);
152: newRepositories.put(entrypath[depth], rep);
153: }
154: }
155: }
156:
157: repositories = (Repository[]) newRepositories
158: .values()
159: .toArray(new Repository[newRepositories.size()]);
160: resources = newResources;
161:
162: } catch (Exception ex) {
163: ex.printStackTrace();
164: repositories = emptyRepositories;
165: if (resources == null) {
166: resources = new HashMap();
167: } else {
168: resources.clear();
169: }
170:
171: } finally {
172: try {
173: // unlocks the zip file in the underlying filesystem
174: zipfile.close();
175: } catch (Exception ex) {
176: }
177: }
178: }
179: }
180:
181: private ZipEntry composeChildEntry(String name) {
182: if (entryPath == null || entryPath.length() == 0) {
183: return new ZipEntry(name);
184: } else if (entryPath.endsWith("/")) {
185: return new ZipEntry(entryPath + name);
186: } else {
187: return new ZipEntry(entryPath + "/" + name);
188: }
189: }
190:
191: /**
192: * Called to create a child resource for this repository
193: */
194: protected Resource createResource(String name) {
195: return new ZipResource(entryPath + "/" + name, this );
196: }
197:
198: public long getChecksum() {
199: return file.lastModified();
200: }
201:
202: public boolean exists() {
203: ZipFile zipfile = null;
204: try {
205: /* a ZipFile needs to be created to see if the zip file actually
206: exists; this is not cached to provide blocking the zip file in
207: the underlying filesystem */
208: zipfile = getZipFile();
209: return true;
210: } catch (IOException ex) {
211: return false;
212: } finally {
213: try {
214: // unlocks the zip file in the underlying filesystem
215: zipfile.close();
216: } catch (Exception ex) {
217: return false;
218: }
219: }
220: }
221:
222: public void create() {
223: // we do not create zip files as it makes no sense
224: throw new UnsupportedOperationException(
225: "create() not implemented for ZipRepository");
226: }
227:
228: /**
229: * Checks wether the repository is to be considered a top-level
230: * repository from a scripting point of view. For example, a zip
231: * file within a file repository is not a root repository from
232: * a physical point of view, but from the scripting point of view it is.
233: *
234: * @return true if the repository is to be considered a top-level script repository
235: */
236: public boolean isScriptRoot() {
237: return depth == 0;
238: }
239:
240: public long lastModified() {
241: return file.lastModified();
242: }
243:
244: public int hashCode() {
245: return 17 + (37 * file.hashCode()) + (37 * name.hashCode());
246: }
247:
248: public boolean equals(Object obj) {
249: if (!(obj instanceof ZipRepository)) {
250: return false;
251: }
252:
253: ZipRepository rep = (ZipRepository) obj;
254: return (file.equals(rep.file) && name.equals(rep.name));
255: }
256:
257: public String toString() {
258: return new StringBuffer("ZipRepository[").append(name).append(
259: "]").toString();
260: }
261:
262: }
|