blob: 47614588f38567d665c785dc5683198243d2171a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. 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.security.cert.Certificate;
import java.util.Date;
import java.util.jar.Manifest;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jackrabbit.net.URLFactory;
/**
* The <code>ClassLoaderResource</code> class represents a resource looked up
* by the {@link ClassPathEntry}s of the {@link RepositoryClassLoader}. The
* class provides transparent access to the resource irrespective of the fact
* on whether the resource is contained in a repository property or in an
* JAR or ZIP archive.
* <p>
* This class is extended to implement depending features such as storing
* resources in repository properties or JAR or ZIP archives.
*
* @author Felix Meschberger
*/
class ClassLoaderResource {
/** default log category */
private static final Log log = LogFactory.getLog(ClassLoaderResource.class);
/**
* The class path entry which loaded this class loader resource
*/
private final ClassPathEntry pathEntry;
/**
* The name of this resource.
*/
private final String name;
/**
* The repository property providing the resource's contents. This may be
* <code>null</code> if the resource was loaded from a JAR/ZIP archive.
*/
private final Property resProperty;
/**
* The class optionally loaded/defined through this resource.
*
* @see #getLoadedClass()
* @see #setLoadedClass(Class)
*/
private Class loadedClass;
/**
* The time in milliseconds at which this resource has been loaded from
* the repository.
*/
private final long loadTime;
/**
* Creates an instance of this class for the class path entry.
*
* @param pathEntry The {@link ClassPathEntry} of the code source of this
* class loader resource.
* @param name The path name of this resource.
* @param resProperty The <code>Property</code>providing the content's of
* this resource. This may be <code>null</code> if the resource
* was loaded from an JAR or ZIP archive.
*/
/* package */ ClassLoaderResource(ClassPathEntry pathEntry, String name,
Property resProperty) {
this.pathEntry = pathEntry;
this.name = name;
this.resProperty = resProperty;
this.loadTime = System.currentTimeMillis();
}
/**
* Returns the {@link ClassPathEntry} which loaded this resource.
*/
protected ClassPathEntry getClassPathEntry() {
return pathEntry;
}
/**
* Returns the name of this resource. This is the name used to find the
* resource, for example the class name or the properties file path.
*/
public String getName() {
return name;
}
/**
* Returns the <code>Property</code> with which this resource is created.
*/
protected Property getProperty() {
return resProperty;
}
/**
* Returns the time in milliseconds at which this resource has been loaded
*/
protected long getLoadTime() {
return loadTime;
}
/**
* Returns the URL to access this resource, for example a JCR or a JAR URL.
* If the URL cannot be created from the resource data, <code>null</code> is
* returned.
*/
public URL getURL() {
try {
return URLFactory.createURL(getClassPathEntry().session, getPath());
} catch (Exception e) {
log.warn("getURL: Cannot getURL for " + getPath(), e);
}
return null;
}
/**
* Returns the URL to the code source of this entry. If there is no code
* source available, <code>null</code> is returned.
* <p>
* This base class implementation returns the result of calling
* {@link ClassPathEntry#toURL()} on the class path entry from which this
* resource was loaded.
*/
public URL getCodeSourceURL() {
return getClassPathEntry().toURL();
}
/**
* Returns an <code>InputStream</code> to read from the resource.
* <p>
* This base class implementation returns the result of calling the
* <code>getStream()</code> method on the resource's property or
* <code>null</code> if the property is not set.
*/
public InputStream getInputStream() throws RepositoryException {
return (getProperty() != null) ? getProperty().getStream() : null;
}
/**
* Returns the size of the resource or -1 if the size cannot be found out.
* <p>
* This base class implementation returns the result of calling the
* <code>getLength()</code> method on the resource's property or -1 if
* the property is not set.
*
* @throws RepositoryException If an error occurrs trying to find the length
* of the property.
*/
public int getContentLength() throws RepositoryException {
return (getProperty() != null) ? (int) getProperty().getLength() : -1;
}
/**
* Returns the path of the property containing the resource.
* <p>
* This base class implementation returns the absolute path of the
* resource's property. If the property is not set or if an error occurrs
* accesing the property's path, the concatentation of the class path
* entry's path and the resource's name is returned.
*/
public String getPath() {
if (getProperty() != null) {
try {
return getProperty().getPath();
} catch (RepositoryException re) {
// fallback
log.warn("getPath: Cannot retrieve path of entry " + getName(),
re);
}
}
// fallback if no resource property or an error accessing the path of
// the property
return getClassPathEntry().getPath() + getName();
}
/**
* Returns the time of the the last modification of the resource or -1 if
* the last modification time cannot be evaluated.
* <p>
* This base class implementation returns the result of calling the
* {@link Util#getLastModificationTime(Property)} method on the resource's
* property if not <code>null</code>. In case of an error or if the
* property is <code>null</code>, -1 is returned.
*/
public long getLastModificationTime() {
if (getProperty() != null) {
try {
return Util.getLastModificationTime(getProperty());
} catch (RepositoryException re) {
log.info("getLastModificationTime of resource property", re);
}
}
// cannot find the resource modification time, use epoch
return -1;
}
/**
* Returns the resource as an array of bytes
*/
public byte[] getBytes() throws IOException, RepositoryException {
InputStream in = null;
byte[] buf = null;
log.debug("getBytes");
try {
in = getInputStream();
log.debug("getInputStream() returned " + in);
int length = getContentLength();
log.debug("getContentLength() returned " + String.valueOf(length));
if (length >= 0) {
buf = new byte[length];
for (int read; length > 0; length -= read) {
read = in.read(buf, buf.length - length, length);
if (read == -1) {
throw new IOException("unexpected EOF");
}
}
} else {
buf = new byte[1024];
int count = 0;
int read;
// read enlarging buffer
while ((read = in.read(buf, count, buf.length - count)) != -1) {
count += read;
if (count >= buf.length) {
byte buf1[] = new byte[count * 2];
System.arraycopy(buf, 0, buf1, 0, count);
buf = buf1;
}
}
// resize buffer if too big
if (count != buf.length) {
byte buf1[] = new byte[count];
System.arraycopy(buf, 0, buf1, 0, count);
buf = buf1;
}
}
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignore) {
}
}
}
return buf;
}
/**
* Returns the manifest from the jar file for this class resource. If this
* resource is not from a jar file, the method returns <code>null</code>,
* which is what the default implementation does.
*/
public Manifest getManifest() {
return null;
}
/**
* Returns the certificates from the jar file for this class resource. If
* this resource is not from a jar file, the method returns
* <code>null</code>, which is what the default implementation does.
*/
public Certificate[] getCertificates() {
return null;
}
/**
* Returns the <code>Property</code> which is used to check whether this
* resource is expired or not.
* <p>
* This base class method returns the same property as returned by the
* {@link #getProperty()} method. This method may be overwritten by
* implementations as appropriate.
*
* @see #isExpired()
*/
protected Property getExpiryProperty() {
return getProperty();
}
/**
* Returns <code>true</code> if the last modification date of the expiry
* property of this resource is loaded is later than the time at which this
* resource has been loaded. If the last modification time of the expiry
* property cannot be calculated or if an error occurrs checking the expiry
* propertiy's last modification time, <code>true</code> is returned.
*/
public boolean isExpired() {
try {
// creation time of version if loaded now
long currentPropTime = 0;
Property prop = getExpiryProperty();
if (prop != null) {
currentPropTime = Util.getLastModificationTime(prop);
}
// creation time of version currently loaded
long loadTime = getLoadTime();
// expire if a new version would be loaded
boolean exp = currentPropTime > loadTime;
if (exp && log.isDebugEnabled()) {
log.debug("expireResource: Resource created " +
new Date(loadTime) + " superceded by version created " +
new Date(currentPropTime));
}
// return the result of checking
return exp;
} catch (RepositoryException re) {
log.debug("expireResource: Cannot get current version for " +
toString() + ", will expire: " + re);
return true;
}
}
/**
* Returns the class which was loaded through this resource. It is expected
* that the class loader sets the class which was loaded through this
* resource by calling the {@link #setLoadedClass(Class)} method. If this
* class was not used to load a class or if the class loader failed to
* set the class loaded through this resoource, this method will return
* <code>null</code>.
*
* @return The class loaded through this resource, which may be
* <code>null</code> if this resource was never used to load a class
* or if the loader failed to set class through the
* {@link #setLoadedClass(Class)} method.
*
* @see #setLoadedClass(Class)
*/
public Class getLoadedClass() {
return loadedClass;
}
/**
* Sets the class which was loaded through this resource. This method does
* not check, whether it is plausible that this class was actually loaded
* from this resource, nor does this method check whether the class object
* is <code>null</code> or not.
*
* @param loadedClass The class to be loaded.
*/
public void setLoadedClass(Class loadedClass) {
this.loadedClass = loadedClass;
}
/**
* Returns the <code>String</code> representation of this resource.
*/
public String toString() {
StringBuffer buf = new StringBuffer(getClass().getName());
buf.append(": path=");
buf.append(getPath());
buf.append(", name=");
buf.append(getName());
return buf.toString();
}
}