/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * Free SoftwareFoundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.jca.ra; import com.caucho.config.Config; import com.caucho.config.ConfigException; import com.caucho.jca.cfg.AdminObjectConfig; import com.caucho.jca.cfg.ConnectionDefinition; import com.caucho.jca.cfg.ConnectorConfig; import com.caucho.jca.cfg.MessageListenerConfig; import com.caucho.jca.cfg.ResourceAdapterConfig; import com.caucho.loader.DynamicClassLoader; import com.caucho.loader.EnvironmentBean; import com.caucho.util.L10N; import com.caucho.vfs.Jar; import com.caucho.vfs.Path; import com.caucho.vfs.ReadStream; import com.caucho.vfs.WriteStream; import javax.annotation.PostConstruct; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * A resource archive (rar) */ public class ResourceArchive implements EnvironmentBean { static final L10N L = new L10N(ResourceArchive.class); static final Logger log = Logger.getLogger(ResourceArchive.class.getName()); private ClassLoader _loader; private Path _rootDir; private Path _rarPath; private ConnectorConfig _config; /** * Creates the application. */ ResourceArchive() { _loader = Thread.currentThread().getContextClassLoader(); } /** * Sets the root directory. */ public void setRootDirectory(Path rootDir) { _rootDir = rootDir; } /** * Sets the root directory. */ public Path getRootDirectory() { return _rootDir; } /** * Returns the class loader. */ public ClassLoader getClassLoader() { return _loader; } /** * Sets the path to the .ear file */ public void setRarPath(Path rarPath) { _rarPath = rarPath; } /** * Returns the name. */ public String getDisplayName() { return _config.getDisplayName(); } /** * Returns the resource adapter class. */ public ResourceAdapterConfig getResourceAdapter() { return _config.getResourceAdapter(); } /** * Returns the resource adapter class. */ public Class getResourceAdapterClass() { return _config.getResourceAdapter().getResourceadapterClass(); } /** * Returns the transaction support. */ public String getTransactionSupport() { if (getResourceAdapter() != null) return getResourceAdapter().getTransactionSupport(); else return null; } /** * Returns the matching connection factory class. */ public ConnectionDefinition getConnectionDefinition(String type) { ResourceAdapterConfig raConfig = _config.getResourceAdapter(); if (raConfig != null) return raConfig.getConnectionDefinition(type); else return null; } /** * Returns the activation spec. */ public MessageListenerConfig getMessageListener(String type) { ResourceAdapterConfig raConfig = _config.getResourceAdapter(); if (raConfig != null) return raConfig.getMessageListener(type); else return null; } /** * Returns the managed object definition. */ public AdminObjectConfig getAdminObject(String type) { ResourceAdapterConfig raConfig = _config.getResourceAdapter(); if (raConfig != null) return raConfig.getAdminObject(type); else return null; } /** * Configures the resource. */ @PostConstruct public void init() throws ConfigException { try { expandRar(); ClassLoader loader = Thread.currentThread().getContextClassLoader(); for (; loader != null; loader = loader.getParent()) { if (loader instanceof DynamicClassLoader) break; } if (loader == null) throw new ConfigException(L.l("loader issues with resource adapter")); addJars((DynamicClassLoader) loader, _rootDir); addNative((DynamicClassLoader) loader, _rootDir); Path raXml = _rootDir.lookup("META-INF/ra.xml"); if (! raXml.canRead()) throw new ConfigException(L.l("missing ra.xml for rar {0}. .rar files require a META-INF/ra.xml file.", _rarPath)); _config = new ConnectorConfig(); new Config().configure(_config, raXml, "com/caucho/jca/jca.rnc"); } catch (ConfigException e) { throw e; } catch (Exception e) { throw ConfigException.create(e); } log.info("ResourceArchive[" + _config.getDisplayName() + "] loaded"); } void destroy() { } /** * Adds the jars from the rar file to the class loader. */ private void addJars(DynamicClassLoader loader, Path path) throws IOException { if (path.getPath().endsWith(".jar")) { loader.addJar(path); } else if (path.isDirectory()) { String []list = path.list(); for (int i = 0; i < list.length; i++) addJars(loader, path.lookup(list[i])); } } /** * Adds the native paths from the rar file to the class loader. */ private void addNative(DynamicClassLoader loader, Path path) throws IOException { String fileName = path.getPath(); if (fileName.endsWith(".so") || fileName.endsWith(".dll") || fileName.endsWith(".jnilib")) { loader.addNative(path); } else if (path.isDirectory()) { String []list = path.list(); for (int i = 0; i < list.length; i++) addNative(loader, path.lookup(list[i])); } } /** * Expand an rar file. The _rarExpandLock must be obtained before the * expansion. * * @param rar the rar file * @param expandDir the directory which will contain the rar contents */ private void expandRar() throws IOException { Path rar = _rarPath; if (! rar.canRead()) return; try { _rootDir.mkdirs(); } catch (Throwable e) { } Path expandDir = _rootDir; Path tempDir = _rootDir.getParent().lookup(".temp"); Path dependPath = _rootDir.lookup("META-INF/resin-rar.timestamp"); // XXX: change to a hash if (dependPath.canRead()) { ReadStream is = null; ObjectInputStream ois = null; try { is = dependPath.openRead(); ois = new ObjectInputStream(is); long lastModified = ois.readLong(); long length = ois.readLong(); if (lastModified == rar.getLastModified() && length == rar.getLength()) return; } catch (IOException e) { } finally { try { if (ois != null) ois.close(); } catch (IOException e) { } if (is != null) is.close(); } } try { if (log.isLoggable(Level.INFO)) log.info("expanding rar " + rar + " to " + tempDir); if (! tempDir.equals(expandDir)) { tempDir.removeAll(); } tempDir.mkdirs(); ReadStream rs = rar.openRead(); ZipInputStream zis = new ZipInputStream(rs); try { ZipEntry entry; byte []buffer = new byte[1024]; while ((entry = zis.getNextEntry()) != null) { String name = entry.getName(); Path path = tempDir.lookup(name); if (entry.isDirectory()) path.mkdirs(); else { long length = entry.getSize(); long lastModified = entry.getTime(); path.getParent().mkdirs(); WriteStream os = path.openWrite(); try { int len; while ((len = zis.read(buffer, 0, buffer.length)) > 0) os.write(buffer, 0, len); } catch (IOException e) { log.log(Level.FINE, e.toString(), e); } finally { os.close(); } if (lastModified > 0) path.setLastModified(lastModified); } } } finally { try { zis.close(); } catch (IOException e) { } rs.close(); } if (! tempDir.equals(expandDir)) { if (log.isLoggable(Level.INFO)) log.info("moving rar " + rar + " to " + expandDir); // Close the cached zip streams because on NT that can lock // the filesystem. try { Jar.clearJarCache(); removeAll(expandDir); } catch (Throwable e) { Jar.clearJarCache(); removeAll(expandDir); } moveAll(tempDir, expandDir); removeAll(tempDir); } } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); // If the jar is incomplete, it should throw an exception here. return; } try { dependPath.getParent().mkdirs(); WriteStream os = dependPath.openWrite(); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeLong(rar.getLastModified()); oos.writeLong(rar.getLength()); oos.close(); os.close(); } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); } } /** * Recursively remove all files in a directory. Used for wars when * they change. * * @param dir root directory to start removal */ private static void removeAll(Path path) { try { if (path.isDirectory()) { String []list = path.list(); for (int i = 0; list != null && i < list.length; i++) { removeAll(path.lookup(list[i])); } } path.remove(); } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); } } /** * Move directory A to directory B. * * @param source source directory * @param target target directory */ private static void moveAll(Path source, Path target) { try { if (source.isDirectory()) { try { target.mkdirs(); } catch (IOException e) { } String []list = source.list(); for (int i = 0; list != null && i < list.length; i++) { moveAll(source.lookup(list[i]), target.lookup(list[i])); } } else source.renameTo(target); } catch (IOException e) { log.log(Level.WARNING, e.toString(), e); } } public String toString() { return getClass().getSimpleName() + "[" + getResourceAdapterClass() + "]"; } }