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: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.jca;
030:
031: import com.caucho.config.Config;
032: import com.caucho.config.ConfigException;
033: import com.caucho.jca.cfg.AdminObjectConfig;
034: import com.caucho.jca.cfg.ConnectionDefinition;
035: import com.caucho.jca.cfg.ConnectorConfig;
036: import com.caucho.jca.cfg.MessageListenerConfig;
037: import com.caucho.jca.cfg.ResourceAdapterConfig;
038: import com.caucho.loader.DynamicClassLoader;
039: import com.caucho.loader.EnvironmentBean;
040: import com.caucho.log.Log;
041: import com.caucho.util.L10N;
042: import com.caucho.vfs.Jar;
043: import com.caucho.vfs.Path;
044: import com.caucho.vfs.ReadStream;
045: import com.caucho.vfs.WriteStream;
046:
047: import javax.annotation.PostConstruct;
048: import java.io.IOException;
049: import java.io.ObjectInputStream;
050: import java.io.ObjectOutputStream;
051: import java.util.logging.Level;
052: import java.util.logging.Logger;
053: import java.util.zip.ZipEntry;
054: import java.util.zip.ZipInputStream;
055:
056: /**
057: * A resource archive (rar)
058: */
059: public class ResourceArchive implements EnvironmentBean {
060: static final L10N L = new L10N(ResourceArchive.class);
061: static final Logger log = Logger.getLogger(ResourceArchive.class
062: .getName());
063:
064: private ClassLoader _loader;
065:
066: private Path _rootDir;
067:
068: private Path _rarPath;
069:
070: private ConnectorConfig _config;
071:
072: /**
073: * Creates the application.
074: */
075: ResourceArchive() {
076: _loader = Thread.currentThread().getContextClassLoader();
077: }
078:
079: /**
080: * Sets the root directory.
081: */
082: public void setRootDirectory(Path rootDir) {
083: _rootDir = rootDir;
084: }
085:
086: /**
087: * Sets the root directory.
088: */
089: public Path getRootDirectory() {
090: return _rootDir;
091: }
092:
093: /**
094: * Returns the class loader.
095: */
096: public ClassLoader getClassLoader() {
097: return _loader;
098: }
099:
100: /**
101: * Sets the path to the .ear file
102: */
103: public void setRarPath(Path rarPath) {
104: _rarPath = rarPath;
105: }
106:
107: /**
108: * Returns the name.
109: */
110: public String getDisplayName() {
111: return _config.getDisplayName();
112: }
113:
114: /**
115: * Returns the resource adapter class.
116: */
117: public ResourceAdapterConfig getResourceAdapter() {
118: return _config.getResourceAdapter();
119: }
120:
121: /**
122: * Returns the resource adapter class.
123: */
124: public Class getResourceAdapterClass() {
125: return _config.getResourceAdapter().getResourceadapterClass();
126: }
127:
128: /**
129: * Returns the transaction support.
130: */
131: public String getTransactionSupport() {
132: if (getResourceAdapter() != null)
133: return getResourceAdapter().getTransactionSupport();
134: else
135: return null;
136: }
137:
138: /**
139: * Returns the matching connection factory class.
140: */
141: public ConnectionDefinition getConnectionDefinition(String type) {
142: ResourceAdapterConfig raConfig = _config.getResourceAdapter();
143:
144: if (raConfig != null)
145: return raConfig.getConnectionDefinition(type);
146: else
147: return null;
148: }
149:
150: /**
151: * Returns the activation spec.
152: */
153: public MessageListenerConfig getMessageListener(String type) {
154: ResourceAdapterConfig raConfig = _config.getResourceAdapter();
155:
156: if (raConfig != null)
157: return raConfig.getMessageListener(type);
158: else
159: return null;
160: }
161:
162: /**
163: * Returns the managed object definition.
164: */
165: public AdminObjectConfig getAdminObject(String type) {
166: ResourceAdapterConfig raConfig = _config.getResourceAdapter();
167:
168: if (raConfig != null)
169: return raConfig.getAdminObject(type);
170: else
171: return null;
172: }
173:
174: /**
175: * Configures the resource.
176: */
177: @PostConstruct
178: public void init() throws ConfigException {
179: try {
180: expandRar();
181:
182: ClassLoader loader = Thread.currentThread()
183: .getContextClassLoader();
184: for (; loader != null; loader = loader.getParent()) {
185: if (loader instanceof DynamicClassLoader)
186: break;
187: }
188:
189: if (loader == null)
190: throw new ConfigException(L
191: .l("loader issues with resource adapter"));
192:
193: addJars((DynamicClassLoader) loader, _rootDir);
194: addNative((DynamicClassLoader) loader, _rootDir);
195:
196: Path raXml = _rootDir.lookup("META-INF/ra.xml");
197:
198: if (!raXml.canRead())
199: throw new ConfigException(
200: L
201: .l(
202: "missing ra.xml for rar {0}. .rar files require a META-INF/ra.xml file.",
203: _rarPath));
204:
205: _config = new ConnectorConfig();
206:
207: new Config().configure(_config, raXml,
208: "com/caucho/jca/jca.rnc");
209: } catch (ConfigException e) {
210: throw e;
211: } catch (Exception e) {
212: throw ConfigException.create(e);
213: }
214:
215: log.info("ResourceArchive[" + _config.getDisplayName()
216: + "] loaded");
217: }
218:
219: void destroy() {
220: }
221:
222: /**
223: * Adds the jars from the rar file to the class loader.
224: */
225: private void addJars(DynamicClassLoader loader, Path path)
226: throws IOException {
227: if (path.getPath().endsWith(".jar")) {
228: loader.addJar(path);
229: } else if (path.isDirectory()) {
230: String[] list = path.list();
231:
232: for (int i = 0; i < list.length; i++)
233: addJars(loader, path.lookup(list[i]));
234: }
235: }
236:
237: /**
238: * Adds the native paths from the rar file to the class loader.
239: */
240: private void addNative(DynamicClassLoader loader, Path path)
241: throws IOException {
242: String fileName = path.getPath();
243:
244: if (fileName.endsWith(".so") || fileName.endsWith(".dll")
245: || fileName.endsWith(".jnilib")) {
246: loader.addNative(path);
247: }
248:
249: else if (path.isDirectory()) {
250: String[] list = path.list();
251:
252: for (int i = 0; i < list.length; i++)
253: addNative(loader, path.lookup(list[i]));
254: }
255: }
256:
257: /**
258: * Expand an rar file. The _rarExpandLock must be obtained before the
259: * expansion.
260: *
261: * @param rar the rar file
262: * @param expandDir the directory which will contain the rar contents
263: */
264: private void expandRar() throws IOException {
265: Path rar = _rarPath;
266:
267: if (!rar.canRead())
268: return;
269:
270: try {
271: _rootDir.mkdirs();
272: } catch (Throwable e) {
273: }
274:
275: Path expandDir = _rootDir;
276: Path tempDir = _rootDir.getParent().lookup(".temp");
277: Path dependPath = _rootDir
278: .lookup("META-INF/resin-rar.timestamp");
279:
280: // XXX: change to a hash
281: if (dependPath.canRead()) {
282: ReadStream is = null;
283: ObjectInputStream ois = null;
284: try {
285: is = dependPath.openRead();
286: ois = new ObjectInputStream(is);
287:
288: long lastModified = ois.readLong();
289: long length = ois.readLong();
290:
291: if (lastModified == rar.getLastModified()
292: && length == rar.getLength())
293: return;
294: } catch (IOException e) {
295: } finally {
296: try {
297: if (ois != null)
298: ois.close();
299: } catch (IOException e) {
300: }
301:
302: if (is != null)
303: is.close();
304: }
305: }
306:
307: try {
308: if (log.isLoggable(Level.INFO))
309: log.info("expanding rar " + rar + " to " + tempDir);
310:
311: if (!tempDir.equals(expandDir)) {
312: tempDir.removeAll();
313: }
314: tempDir.mkdirs();
315:
316: ReadStream rs = rar.openRead();
317: ZipInputStream zis = new ZipInputStream(rs);
318:
319: try {
320: ZipEntry entry;
321:
322: byte[] buffer = new byte[1024];
323:
324: while ((entry = zis.getNextEntry()) != null) {
325: String name = entry.getName();
326: Path path = tempDir.lookup(name);
327:
328: if (entry.isDirectory())
329: path.mkdirs();
330: else {
331: long length = entry.getSize();
332: long lastModified = entry.getTime();
333: path.getParent().mkdirs();
334:
335: WriteStream os = path.openWrite();
336: try {
337: int len;
338: while ((len = zis.read(buffer, 0,
339: buffer.length)) > 0)
340: os.write(buffer, 0, len);
341: } catch (IOException e) {
342: log.log(Level.FINE, e.toString(), e);
343: } finally {
344: os.close();
345: }
346:
347: if (lastModified > 0)
348: path.setLastModified(lastModified);
349: }
350: }
351: } finally {
352: try {
353: zis.close();
354: } catch (IOException e) {
355: }
356:
357: rs.close();
358: }
359:
360: if (!tempDir.equals(expandDir)) {
361: if (log.isLoggable(Level.INFO))
362: log.info("moving rar " + rar + " to " + expandDir);
363:
364: // Close the cached zip streams because on NT that can lock
365: // the filesystem.
366: try {
367: Jar.clearJarCache();
368: removeAll(expandDir);
369: } catch (Throwable e) {
370: Jar.clearJarCache();
371: removeAll(expandDir);
372: }
373:
374: moveAll(tempDir, expandDir);
375: removeAll(tempDir);
376: }
377: } catch (IOException e) {
378: log.log(Level.WARNING, e.toString(), e);
379: // If the jar is incomplete, it should throw an exception here.
380: return;
381: }
382:
383: try {
384: dependPath.getParent().mkdirs();
385: WriteStream os = dependPath.openWrite();
386: ObjectOutputStream oos = new ObjectOutputStream(os);
387:
388: oos.writeLong(rar.getLastModified());
389: oos.writeLong(rar.getLength());
390: oos.close();
391: os.close();
392: } catch (Throwable e) {
393: log.log(Level.WARNING, e.toString(), e);
394: }
395: }
396:
397: /**
398: * Recursively remove all files in a directory. Used for wars when
399: * they change.
400: *
401: * @param dir root directory to start removal
402: */
403: private static void removeAll(Path path) {
404: try {
405: if (path.isDirectory()) {
406: String[] list = path.list();
407: for (int i = 0; list != null && i < list.length; i++) {
408: removeAll(path.lookup(list[i]));
409: }
410: }
411:
412: path.remove();
413: } catch (Throwable e) {
414: log.log(Level.WARNING, e.toString(), e);
415: }
416: }
417:
418: /**
419: * Move directory A to directory B.
420: *
421: * @param source source directory
422: * @param target target directory
423: */
424: private static void moveAll(Path source, Path target) {
425: try {
426: if (source.isDirectory()) {
427: try {
428: target.mkdirs();
429: } catch (IOException e) {
430: }
431:
432: String[] list = source.list();
433: for (int i = 0; list != null && i < list.length; i++) {
434: moveAll(source.lookup(list[i]), target
435: .lookup(list[i]));
436: }
437: } else
438: source.renameTo(target);
439: } catch (IOException e) {
440: log.log(Level.WARNING, e.toString(), e);
441: }
442: }
443:
444: public String toString() {
445: return getClass().getSimpleName() + "["
446: + getResourceAdapterClass() + "]";
447: }
448: }
|