/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.apache.jackrabbit.classloader;import java.io.IOException;import java.io.InputStream;import java.net.URL;import java.net.URLConnection;import java.util.Calendar;import java.util.StringTokenizer;import java.util.jar.Manifest;import java.util.zip.ZipEntry;import java.util.zip.ZipInputStream;import javax.jcr.Item;import javax.jcr.Node;import javax.jcr.Property;import javax.jcr.RepositoryException;import javax.jcr.Session;import javax.jcr.Workspace;import javax.jcr.nodetype.NoSuchNodeTypeException;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * The <code>ExpandingArchiveClassPathEntry</code> extends the * {@link org.apache.jackrabbit.classloader.ArchiveClassPathEntry} class with support * to automatically expand the archive (JAR or ZIP) into the repository * below the path entry node. The path used to construct the instance is the * path of an item resolving to a property containing the jar archive to access. * * @author Felix Meschberger * * @see org.apache.jackrabbit.classloader.ArchiveClassPathEntry * @see org.apache.jackrabbit.classloader.ClassPathEntry *//* package */ class ExpandingArchiveClassPathEntry extends ArchiveClassPathEntry { /** The name of the node type required to expand the archive */ public static final String TYPE_JARFILE = "rep:jarFile"; /** The name of the child node taking the expanded archive */ public static final String NODE_JARCONTENTS = "rep:jarContents"; /** * The name of the property taking the time at which the archive was * expanded */ public static final String PROP_EXPAND_DATE = "rep:jarExpanded"; /** Default logger */ private static final Logger log = LoggerFactory.getLogger(ExpandingArchiveClassPathEntry.class); /** The node of the unpacked JAR contents */ private Node jarContents; /** * Creates an instance of the <code>ExpandingArchiveClassPathEntry</code> * class. * * @param prop The <code>Property</code> containing the archive and * the session used to access the repository. * @param path The original class path entry leading to the creation of * this instance. This is not necessairily the same path as the * property's path if the property was found through the primary * item chain. * * @throws RepositoryException If an error occurrs retrieving the session * from the property. */ ExpandingArchiveClassPathEntry(Property prop, String path) throws RepositoryException { super(prop, path); } /** * Clones the indicated <code>ExpandingArchiveClassPathEntry</code> object * by taking over its path, session and property. * * @param base The base <code>ExpandingArchiveClassPathEntry</code> entry * to clone. * * @see ClassPathEntry#ClassPathEntry(ClassPathEntry) */ private ExpandingArchiveClassPathEntry(ExpandingArchiveClassPathEntry base) { super(base); } /** * Returns a {@link ClassLoaderResource} for the named resource if it * can be found in the archive identified by the path given at * construction time. Note that if the archive property would exist but is * not readable by the current session, no resource is returned. * * @param name The name of the resource to return. If the resource would * be a class the name must already be modified to denote a valid * path, that is dots replaced by slashes and the <code>.class</code> * extension attached. * * @return The {@link ClassLoaderResource} identified by the name or * <code>null</code> if no resource is found for that name. */ public ClassLoaderResource getResource(final String name) { try { // find the resource for the name in the expanded archive contents Node jarContents = getJarContents(); Item resItem = null; if (jarContents.hasNode(name)) { resItem = jarContents.getNode(name); } else if (jarContents.hasProperty(name)) { resItem = jarContents.getProperty(name); } // if the name resolved to an item, resolve the item to a // single-valued non-reference property Property resProp = (resItem != null) ? Util.getProperty(resItem) : null; // if found create the resource to return if (resProp != null) { return new ClassLoaderResource(this, name, resProp) { public URL getURL() { return ExpandingArchiveClassPathEntry.this.getURL(getName()); } public URL getCodeSourceURL() { return ExpandingArchiveClassPathEntry.this.getCodeSourceURL(); } public Manifest getManifest() { return ExpandingArchiveClassPathEntry.this.getManifest(); } protected Property getExpiryProperty() { return ExpandingArchiveClassPathEntry.this.getProperty(); } }; } log.debug("getResource: resource {} not found in archive {}", name, path); } catch (RepositoryException re) { log.warn("getResource: problem accessing the archive {} for {}", new Object[] { path, name }, re.toString()); } // invariant : not found or problem accessing the archive return null; } /** * Returns a <code>ClassPathEntry</code> with the same configuration as * this <code>ClassPathEntry</code>. * <p> * The <code>ExpandingArchiveClassPathEntry</code> class has internal state. * Therefore a new instance is created from the unmodifiable configuration * of this instance. */ ClassPathEntry copy() { return new ExpandingArchiveClassPathEntry(this); } //----------- internal helper to find the entry ------------------------ /** * Returns the root node of the expanded archive. If the archive's node * does not contain the expanded archive, it is expanded on demand. If the * archive has already been expanded, it is checked whether it is up to * date and expanded again if not. * * @throws RepositoryException if an error occurrs expanding the archive * into the repository. */ private Node getJarContents() throws RepositoryException { if (jarContents == null) { Node jarNode = null; // the node containing the jar file Node jarRoot = null; // the root node of the expanded contents try { Item jarItem = session.getItem(getPath()); jarNode = (jarItem.isNode()) ? (Node) jarItem : jarItem.getParent(); // if the jar been unpacked once, check for updated jar file, // which must be unpacked if (jarNode.isNodeType(TYPE_JARFILE)) { long lastMod = Util.getLastModificationTime(getProperty()); long expanded = jarNode.getProperty(PROP_EXPAND_DATE).getLong(); // get the content, remove if outdated or use if ok jarRoot = jarNode.getNode(NODE_JARCONTENTS); // if expanded content is outdated, remove it if (lastMod <= expanded) { jarRoot.remove(); jarRoot = null; // have to unpack below } } else if (!jarNode.canAddMixin(TYPE_JARFILE)) { // this is actually a problem, because I expect to be able // to add the mixin node type due to checkExpandArchives // having returned true earlier throw new RepositoryException( "Cannot unpack JAR file contents into " + jarNode.getPath()); } else { jarNode.addMixin(TYPE_JARFILE); jarNode.setProperty(PROP_EXPAND_DATE, Calendar.getInstance());