001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.server.deploy;
031:
032: import com.caucho.config.types.FileSetType;
033: import com.caucho.loader.DynamicClassLoader;
034: import com.caucho.loader.Environment;
035: import com.caucho.util.L10N;
036: import com.caucho.util.Log;
037: import com.caucho.vfs.Depend;
038: import com.caucho.vfs.Path;
039: import com.caucho.vfs.ReadStream;
040: import com.caucho.vfs.Vfs;
041: import com.caucho.vfs.WriteStream;
042:
043: import java.io.IOException;
044: import java.util.jar.Attributes;
045: import java.util.jar.Manifest;
046: import java.util.logging.Level;
047: import java.util.logging.Logger;
048: import java.util.zip.ZipEntry;
049: import java.util.zip.ZipInputStream;
050:
051: /**
052: * A deployment entry that expands from an archive (Jar/Zip) file.
053: */
054: abstract public class ExpandDeployController<I extends DeployInstance>
055: extends DeployController<I> {
056: private static final L10N L = new L10N(ExpandDeployController.class);
057: private static final Logger log = Log
058: .open(ExpandDeployController.class);
059:
060: private Object _archiveExpandLock = new Object();
061:
062: private Path _rootDirectory;
063: private Path _archivePath;
064:
065: private FileSetType _expandCleanupFileSet;
066:
067: // classloader for the manifest entries
068: private DynamicClassLoader _manifestLoader;
069: private Manifest _manifest;
070:
071: protected ExpandDeployController(String id) {
072: this (id, null, null);
073: }
074:
075: protected ExpandDeployController(String id, ClassLoader loader,
076: Path rootDirectory) {
077: super (id, loader);
078:
079: if (rootDirectory == null)
080: rootDirectory = Vfs.getPwd(getParentClassLoader());
081:
082: _rootDirectory = rootDirectory;
083: }
084:
085: /**
086: * Gets the root directory
087: */
088: public Path getRootDirectory() {
089: return _rootDirectory;
090: }
091:
092: /**
093: * Sets the root directory
094: */
095: protected void setRootDirectory(Path rootDirectory) {
096: _rootDirectory = rootDirectory;
097: }
098:
099: /**
100: * Gets the archive path.
101: */
102: public Path getArchivePath() {
103: return _archivePath;
104: }
105:
106: /**
107: * Sets the archive path.
108: */
109: public void setArchivePath(Path path) {
110: _archivePath = path;
111: }
112:
113: /**
114: * Returns the manifest.
115: */
116: public Manifest getManifest() {
117: return _manifest;
118: }
119:
120: /**
121: * Sets the manifest class loader.
122: */
123: public void setManifestClassLoader(DynamicClassLoader loader) {
124: _manifestLoader = loader;
125: }
126:
127: /**
128: * Sets the archive auto-remove file set.
129: */
130: public void setExpandCleanupFileSet(FileSetType fileSet) {
131: _expandCleanupFileSet = fileSet;
132: }
133:
134: /**
135: * Merges with the old controller.
136: */
137: @Override
138: protected void mergeController(DeployController oldControllerV) {
139: super .mergeController(oldControllerV);
140:
141: ExpandDeployController<I> oldController;
142: oldController = (ExpandDeployController<I>) oldControllerV;
143:
144: if (oldController._expandCleanupFileSet != null)
145: _expandCleanupFileSet = oldController._expandCleanupFileSet;
146: }
147:
148: /**
149: * Expand an archive file. The _archiveExpandLock must be obtained
150: * before the expansion.
151: */
152: protected void expandArchive() throws IOException {
153: synchronized (_archiveExpandLock) {
154: if (!expandArchiveImpl()) {
155: try {
156: Thread.sleep(2000);
157: } catch (InterruptedException e) {
158: }
159:
160: expandArchiveImpl();
161: }
162:
163: Path path = getRootDirectory().lookup(
164: "META-INF/MANIFEST.MF");
165: if (path.canRead()) {
166: ReadStream is = path.openRead();
167: try {
168: _manifest = new Manifest(is);
169: } catch (IOException e) {
170: log
171: .warning(L
172: .l(
173: "Manifest file cannot be read for '{0}'.\n{1}",
174: getRootDirectory(), e));
175:
176: log.log(Level.FINE, e.toString(), e);
177: } finally {
178: is.close();
179: }
180: }
181: }
182: }
183:
184: /**
185: * Adds any class path from the manifest.
186: */
187: protected void addManifestClassPath() throws IOException {
188: DynamicClassLoader loader = Environment.getDynamicClassLoader();
189: if (loader == null)
190: return;
191:
192: Manifest manifest = getManifest();
193:
194: if (manifest == null)
195: return;
196:
197: Attributes main = manifest.getMainAttributes();
198:
199: if (main == null)
200: return;
201:
202: String classPath = main.getValue("Class-Path");
203:
204: Path pwd = null;
205:
206: if (getArchivePath() != null)
207: pwd = getArchivePath().getParent();
208: else
209: pwd = getRootDirectory();
210:
211: if (classPath == null) {
212: } else if (_manifestLoader != null)
213: _manifestLoader.addManifestClassPath(classPath, pwd);
214: else
215: loader.addManifestClassPath(classPath, pwd);
216: }
217:
218: /**
219: * Expand an archive. The _archiveExpandLock must be obtained before the
220: * expansion.
221: */
222: private boolean expandArchiveImpl() throws IOException {
223: Path archivePath = getArchivePath();
224:
225: if (archivePath == null)
226: return true;
227:
228: if (!archivePath.canRead())
229: return true;
230:
231: Path expandDir = getRootDirectory();
232: Path parent = expandDir.getParent();
233:
234: try {
235: parent.mkdirs();
236: } catch (Throwable e) {
237: }
238:
239: Path dependPath = expandDir.lookup("META-INF/resin-war.digest");
240: Depend depend = null;
241:
242: // XXX: change to a hash
243: if (dependPath.canRead()) {
244: ReadStream is = null;
245: try {
246: is = dependPath.openRead();
247:
248: String line = is.readLine();
249:
250: long digest;
251:
252: if (line != null) {
253: digest = Long.parseLong(line.trim());
254:
255: depend = new Depend(archivePath, digest);
256:
257: if (!depend.isModified())
258: return true;
259: }
260: } catch (Throwable e) {
261: log.log(Level.FINE, e.toString(), e);
262: } finally {
263: if (is != null)
264: is.close();
265: }
266: }
267:
268: if (depend == null)
269: depend = new Depend(archivePath);
270:
271: try {
272: if (log.isLoggable(Level.INFO))
273: getLog()
274: .info(
275: "expanding " + archivePath + " to "
276: + expandDir);
277:
278: removeExpandDirectory(expandDir);
279:
280: expandDir.mkdirs();
281:
282: ReadStream rs = archivePath.openRead();
283: ZipInputStream zis = new ZipInputStream(rs);
284:
285: try {
286: ZipEntry entry = zis.getNextEntry();
287:
288: byte[] buffer = new byte[1024];
289:
290: while (entry != null) {
291: String name = entry.getName();
292: Path path = expandDir.lookup(name);
293:
294: if (entry.isDirectory())
295: path.mkdirs();
296: else {
297: long entryLength = entry.getSize();
298: long length = entryLength;
299:
300: // XXX: avoids unexpected end of ZLIB input stream.
301: // XXX: This should be a really temp. workaround.
302: int bufferLen = buffer.length;
303:
304: // XXX: avoids unexpected end of ZLIB input stream.
305: if (length < 0) {
306: // bufferLen = 1;
307: length = Long.MAX_VALUE / 2;
308: } else if (length < bufferLen) {
309: bufferLen = (int) length;
310: }
311:
312: long lastModified = entry.getTime();
313: path.getParent().mkdirs();
314:
315: WriteStream os = path.openWrite();
316: int len = 0;
317: try {
318: if (bufferLen == 1) {
319: for (int ch = zis.read(); ch != -1; ch = zis
320: .read())
321: os.write(ch);
322: } else {
323: while ((len = zis.read(buffer, 0,
324: bufferLen)) > 0) {
325: os.write(buffer, 0, len);
326:
327: // XXX: avoids unexpected end of ZLIB input stream.
328: /*
329: if (len < bufferLen) {
330: for (int ch = zis.read(); ch != -1; ch = zis.read())
331: os.write(ch);
332:
333: break;
334: }
335: */
336:
337: length -= len;
338:
339: if (length < bufferLen) {
340: bufferLen = (int) length;
341: }
342: }
343: }
344: } catch (IOException e) {
345: Exception ex = new Exception(
346: "IOException when expanding entry "
347: + entry + " in "
348: + archivePath
349: + ", entry.length: "
350: + entryLength
351: + " entry.compressed: "
352: + entry.getCompressedSize()
353: + ", bufferLen: "
354: + bufferLen
355: + ", read len: " + len
356: + ", remaining: " + length,
357: e);
358:
359: log.log(Level.FINE, ex.toString(), ex);
360: } finally {
361: os.close();
362: }
363:
364: if (lastModified > 0)
365: path.setLastModified(lastModified);
366: }
367:
368: try {
369: entry = zis.getNextEntry();
370: } catch (IOException e) {
371: log.log(Level.FINE, e.toString(), e);
372:
373: // XXX: avoids unexpected end of ZLIB input stream.
374: break;
375: }
376: }
377: } finally {
378: try {
379: zis.close();
380: } catch (IOException e) {
381: }
382:
383: rs.close();
384: }
385: } catch (IOException e) {
386: log.log(Level.WARNING, e.toString(), e);
387: // If the jar is incomplete, it should throw an exception here.
388: return false;
389: }
390:
391: try {
392: dependPath.getParent().mkdirs();
393: WriteStream os = dependPath.openWrite();
394:
395: os.println(depend.getDigest());
396:
397: os.close();
398: } catch (Throwable e) {
399: log.log(Level.WARNING, e.toString(), e);
400: }
401:
402: return true;
403: }
404:
405: /**
406: * Recursively remove all files in a directory. Used for wars when
407: * they change.
408: *
409: * @param path root directory to start removal
410: */
411: protected void removeExpandDirectory(Path path) {
412: String prefix = path.getPath();
413:
414: if (!prefix.endsWith("/"))
415: prefix = prefix + "/";
416:
417: removeExpandDirectory(path, prefix);
418: }
419:
420: /**
421: * Recursively remove all files in a directory. Used for wars when
422: * they change.
423: *
424: * @param dir root directory to start removal
425: */
426: protected void removeExpandDirectory(Path path, String prefix) {
427: try {
428: if (path.isDirectory()) {
429: String[] list = path.list();
430: for (int i = 0; list != null && i < list.length; i++) {
431: removeExpandDirectory(path.lookup(list[i]), prefix);
432: }
433: }
434:
435: removeExpandFile(path, prefix);
436: } catch (Throwable e) {
437: log.log(Level.WARNING, e.toString(), e);
438: }
439: }
440:
441: /**
442: * Removes an expanded file.
443: */
444: protected void removeExpandFile(Path path, String prefix)
445: throws IOException {
446: if (_expandCleanupFileSet == null
447: || _expandCleanupFileSet.isMatch(path, prefix))
448: path.remove();
449: }
450:
451: /**
452: * Returns the hash code.
453: */
454: public int hashCode() {
455: return getId().hashCode();
456: }
457:
458: /**
459: * Returns equality.
460: */
461: public boolean equals(Object o) {
462: // server/125g
463: if (o == null || !getClass().equals(o.getClass()))
464: return false;
465:
466: DeployController controller = (DeployController) o;
467:
468: // XXX: s/b getRootDirectory?
469: return getId().equals(controller.getId());
470: }
471: }
|