incubator-sling-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cziege...@apache.org
Subject svn commit: r902794 [2/2] - in /sling/trunk/bundles/jcr/classloader: ./ src/main/java/org/apache/sling/jcr/classloader/internal/ src/main/java/org/apache/sling/jcr/classloader/internal/net/
Date Mon, 25 Jan 2010 12:43:23 GMT
Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java?rev=902794&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java (added)
+++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java Mon Jan 25 12:43:22 2010
@@ -0,0 +1,166 @@
+/*
+ * 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.sling.jcr.classloader.internal;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFormatException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>Util</code> provides helper methods for the repository
+ * classloader and its class path entry and resource classes.
+ * <p>
+ * This class may not be extended or instantiated, it just contains static
+ * utility methods.
+ */
+public class Util {
+
+    /** default logging */
+    private static final Logger log = LoggerFactory.getLogger(Util.class);
+
+    /** Private constructor to not instantiate */
+    private Util() {
+    }
+
+    /**
+     * Resolves the given <code>item</code> to a <code>Property</code> from
+     * which contents can be read.
+     * <p>
+     * The following mechanism is used to derive the contents:
+     * <ol>
+     * <li>If the <code>item</code> is a property, this property is used</li>
+     * <li>If the <code>item</code> is a node, three steps are tested:
+     * <ol>
+     * <li>If the node has a <code>jcr:content</code> child node, use that
+     * child node in the next steps. Otherwise continue with the node.</li>
+     * <li>Check for a <code>jcr:data</code> property and use that property
+     * if existing.</li>
+     * <li>Otherwise call <code>getPrimaryItem</code> method repeatedly until
+     * a property is returned or until no more primary item is available.</li>
+     * </ol>
+     * </ol>
+     * If no property can be resolved using the above algorithm or if the
+     * resulting property is a multivalue property, <code>null</code> is
+     * returned. Otherwise if the resulting property is a <code>REFERENCE</code>
+     * property, the node referred to is retrieved and this method is called
+     * recursively with the node. Otherwise, the resulting property is returned.
+     * 
+     * @param item The <code>Item</code> to resolve to a <code>Property</code>.
+     * @return The resolved <code>Property</code> or <code>null</code> if
+     *         the resolved property is a multi-valued property or the
+     *         <code>item</code> is a node which cannot be resolved to a data
+     *         property.
+     * @throws ValueFormatException If the <code>item</code> resolves to a
+     *             single-valued <code>REFERENCE</code> type property which
+     *             cannot be resolved to the node referred to.
+     * @throws RepositoryException if another error occurrs accessing the
+     *             repository.
+     */
+    public static Property getProperty(Item item) throws ValueFormatException,
+            RepositoryException {
+
+        Property prop;
+        if (item.isNode()) {
+
+            // check whether the node has a jcr:content node (e.g. nt:file)
+            Node node = (Node) item;
+            if (node.hasNode("jcr:content")) {
+                node = node.getNode("jcr:content");
+            }
+
+            // if the node has a jcr:data property, use that property
+            if (node.hasProperty("jcr:data")) {
+                
+                prop = node.getProperty("jcr:data");
+
+            } else {
+
+                // otherwise try to follow default item trail
+                try {
+                    item = node.getPrimaryItem();
+                    while (item.isNode()) {
+                        item = ((Node) item).getPrimaryItem();
+                    }
+                    prop = (Property) item;
+                } catch (ItemNotFoundException infe) {
+                    // we don't actually care, but log for completeness
+                    log.debug("getProperty: No primary items for "
+                        + node.getPath(), infe);
+                    return null;
+                }
+            }
+
+        } else {
+
+            prop = (Property) item;
+
+        }
+
+        // we get here with a property - otherwise an exception has already
+        // been thrown
+        if (prop.getDefinition().isMultiple()) {
+            log.error("{} is a multivalue property", prop.getPath());
+            return null;
+        } else if (prop.getType() == PropertyType.REFERENCE) {
+            Node node = prop.getNode();
+            log.info("Property {} refers to node {}; finding primary item",
+                prop.getPath(), node.getPath());
+            return getProperty(node);
+        }
+
+        return prop;
+    }
+
+    /**
+     * Returns the last modification time of the property, which is the long
+     * value of the <code>jcr:lastModified</code> property of the parent node
+     * of <code>prop</code>. If the parent node does not have a
+     * <code>jcr:lastModified</code> property the current system time is
+     * returned.
+     * 
+     * @param prop The property for which to return the last modification time.
+     * @return The last modification time of the resource or the current time if
+     *         the parent node of the property does not have a
+     *         <code>jcr:lastModified</code> property.
+     * @throws ItemNotFoundException If the parent node of the property cannot
+     *             be retrieved.
+     * @throws AccessDeniedException If (read) access to the parent node is
+     *             denied.
+     * @throws RepositoryException If any other error occurrs accessing the
+     *             repository to retrieve the last modification time.
+     */
+    public static long getLastModificationTime(Property prop)
+            throws ItemNotFoundException, PathNotFoundException,
+            AccessDeniedException, RepositoryException {
+
+        Node parent = prop.getParent();
+        if (parent.hasProperty("jcr:lastModified")) {
+            return parent.getProperty("jcr:lastModified").getLong();
+        }
+
+        return System.currentTimeMillis();
+    }
+}

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java?rev=902794&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java (added)
+++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java Mon Jan 25 12:43:22 2010
@@ -0,0 +1,312 @@
+/*
+ * 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.sling.jcr.classloader.internal.net;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+import javax.jcr.Session;
+
+/**
+ * The <code>FileParts</code> class provides composing and parsing functionality
+ * to create and analize JCR Repository URL file components.
+ * <p>
+ * The file component of a JCR Repository URL has the format
+ * <pre>
+ *      file = [ "jcr:" [ "//" authority ] ] "/" repository "/" workspace jarpath .
+ *      authority = // URL authority specification
+ *      repository = // URL encoded repository name
+ *      workspace = // URL encoded workspace name
+ *      jarpath = path [ "!/" [ entry ] ] .
+ *      path = // The absolute item path (with a leading slash)
+ *      entry = // The (relative) path to the entry in an archive
+ * </pre>
+ * <p>
+ * To facitility use of this class with JCRJar URLs, the
+ * {@link #FileParts(String)} supports file specifications which contains
+ * the JCR Repository URL scheme name and an optional URL authority
+ * specification. This prefix in front of the real file specification is
+ * silently discarded. It is not included in the string representation returned
+ * by the {@link #toString()} method.
+ * <p>
+ * To make sure parsing is not complicated by implementation and use case
+ * specific repository and workspace names, those names are URL encoded using
+ * the <code>URLEncoder</code> class and <i>UTF-8</i> character encoding.
+ *
+ * @author Felix Meschberger
+ */
+class FileParts {
+
+    /** The decoded name of the repository */
+    private final String repository;
+
+    /** The decoded name of the workspace */
+    private final String workspace;
+
+    /** The repository item path part of the URL path */
+    private final String path;
+
+    /**
+     * The path to the entry in the archive, if the file spec contains the
+     * jar entry separator <i>!/</i>. If no entry path is specified, this is
+     * <code>null</code>. If no path is specified after the <i>!/</i> this
+     * is an empty string.
+     */
+    private final String entryPath;
+
+    /**
+     * Creates a new instance for the root node of the given session. The
+     * repository name is currently set to the fixed string "_" as there has not
+     * been established a repository naming convention yet. The name of the
+     * workspace is set to the name of the workspace to which the session is
+     * attached. The path is set to <code>"/"</code> to indicate the root node
+     * if the <code>path</code> argument is <code>null</code>.
+     *
+     * @param session The session for which to create this instance.
+     * @param path The absolute item path to initialize this instance with. If
+     *      <code>null</code> the item path is set to the <code>/</code>.
+     * @param entryPath The path to the archive entry to set on this instance.
+     *      This is expected to be a relative path without a leading slash and
+     *      may be <code>null</code>.
+     *
+     * @throws NullPointerException if <code>session</code> is
+     *      <code>null</code>.
+     */
+    FileParts(Session session, String path, String entryPath) {
+        this.repository = "_";
+        this.workspace = session.getWorkspace().getName();
+        this.path = (path == null) ? "/" : path;
+        this.entryPath = entryPath;
+    }
+
+    /**
+     * Creates an instance of this class setting the repository, workspace and
+     * path fields from the given <code>file</code> specification.
+     *
+     * @param file The specification providing the repository, workspace and
+     *      path values.
+     *
+     * @throws NullPointerException if <code>file</code> is
+     *      <code>null</code>.
+     * @throws IllegalArgumentException if <code>file</code> is not the
+     *      correct format.
+     */
+    FileParts(String file) {
+        if (!file.startsWith("/")) {
+            if (file.startsWith(URLFactory.REPOSITORY_SCHEME+":")) {
+                file = strip(file);
+            } else {
+                throw failure("Not an absolute file", file);
+            }
+        }
+
+        // find the repository name
+        int slash0 = 1;
+        int slash1 = file.indexOf('/', slash0);
+        if (slash1 < 0 || slash1-slash0 == 0) {
+            throw failure("Missing repository name", file);
+        }
+        this.repository = decode(file.substring(slash0, slash1));
+
+        // find the workspace name
+        slash0 = slash1 + 1;
+        slash1 = file.indexOf('/', slash0);
+        if (slash1 < 0 || slash1-slash0 == 0) {
+            throw failure("Missing workspace name", file);
+        }
+        this.workspace = decode(file.substring(slash0, slash1));
+
+        String fullPath = file.substring(slash1);
+        int bangSlash = JCRJarURLHandler.indexOfBangSlash(fullPath);
+        if (bangSlash < 0) {
+            this.path = fullPath;
+            this.entryPath = null;
+        } else {
+            this.path = fullPath.substring(0, bangSlash-1);
+            this.entryPath = fullPath.substring(bangSlash+1);
+        }
+    }
+
+    /**
+     * Returns the plain name of the repository.
+     */
+    String getRepository() {
+        return repository;
+    }
+
+    /**
+     * Returns the plain name of the workspace.
+     */
+    String getWorkspace() {
+        return workspace;
+    }
+
+    /**
+     * Returns the absolute repository path of the item.
+     */
+    String getPath() {
+        return path;
+    }
+
+    /**
+     * Returns the entry path of <code>null</code> if no entry exists.
+     */
+    String getEntryPath() {
+        return entryPath;
+    }
+
+    //---------- Object overwrites --------------------------------------------
+
+    /**
+     * Returns a hash code for this instance composed of the hash codes of the
+     * repository, workspace and path names.
+     */
+    public int hashCode() {
+        return getRepository().hashCode() +
+            17 * getWorkspace().hashCode() +
+            33 * getPath().hashCode();
+    }
+
+    /**
+     * Returns <code>true</code> if <code>obj</code> is the same as this or
+     * if other is a <code>FileParts</code> with the same path, workspace and
+     * repository. Otherwise <code>false</code> is returned.
+     */
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        } else if (obj instanceof FileParts) {
+            FileParts other = (FileParts) obj;
+
+            // catch null entry path, fail if other has a defined entry path
+            if (getEntryPath() == null) {
+                if (other.getEntryPath() != null) {
+                    return false;
+                }
+            }
+
+            return getPath().equals(other.getPath()) &&
+                getWorkspace().equals(other.getWorkspace()) &&
+                getRepository().equals(other.getRepository()) &&
+                getEntryPath().equals(other.getEntryPath());
+        }
+
+        // fall back on null or other class
+        return false;
+    }
+
+    /**
+     * Returns the encoded string representation of this instance, which may
+     * later be fed to the {@link #FileParts(String)} constructor to recreate
+     * an equivalent instance.
+     */
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+        buf.append('/').append(encode(getRepository()));
+        buf.append('/').append(encode(getWorkspace()));
+        buf.append(getPath());
+
+        if (getEntryPath() != null) {
+            buf.append("!/").append(getEntryPath());
+        }
+
+        return buf.toString();
+    }
+
+    //---------- internal -----------------------------------------------------
+
+    /**
+     * @throws IllegalArgumentException If there is no path element after the
+     *      authority.
+     */
+    private String strip(String file) {
+        // cut off jcr: prefix - any other prefix, incl. double slash
+        // would cause an exception to be thrown in the constructor
+        int start = 4;
+
+        // check whether the remainder contains an authority specification
+        if (file.length() >= start+2 && file.charAt(start) == '/' &&
+                file.charAt(start+1) == '/') {
+
+            // find the slash after the authority, fail if missing
+            start = file.indexOf('/', start + 2);
+            if (start < 0) {
+                throw failure("Missing path after authority", file);
+            }
+        }
+
+        // return the file now
+        return file.substring(start);
+    }
+
+    /**
+     * Encodes the given string value using the <code>URLEncoder</code> and
+     * <i>UTF-8</i> character encoding.
+     *
+     * @param value The string value to encode.
+     *
+     * @return The encoded string value.
+     *
+     * @throws InternalError If <code>UTF-8</code> character set encoding is
+     *      not supported. As <code>UTF-8</code> is required to be implemented
+     *      on any Java platform, this error is not expected.
+     */
+    private String encode(String value) {
+        try {
+            return URLEncoder.encode(value, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            // not expected, throw an InternalError
+            throw new InternalError("UTF-8 not supported");
+        }
+    }
+
+    /**
+     * Decodes the given string value using the <code>URLDecoder</code> and
+     * <i>UTF-8</i> character encoding.
+     *
+     * @param value The string value to decode.
+     *
+     * @return The decoded string value.
+     *
+     * @throws InternalError If <code>UTF-8</code> character set encoding is
+     *      not supported. As <code>UTF-8</code> is required to be implemented
+     *      on any Java platform, this error is not expected.
+     */
+    private String decode(String value) {
+        try {
+            return URLDecoder.decode(value, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            // not expected, throw an InternalError
+            throw new InternalError("UTF-8 not supported");
+        }
+    }
+
+    /**
+     * Returns a <code>IllegalArgumentException</code> formatted with the
+     * given reason and causing file specification.
+     *
+     * @param reason The failure reason.
+     * @param file The original file specification leading to failure.
+     *
+     * @return A <code>IllegalArgumentException</code> with the given
+     *      reason and causing file specification.
+     */
+    private IllegalArgumentException failure(String reason, String file) {
+        return new IllegalArgumentException(reason + ": '" + file + "'");
+    }
+}

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java?rev=902794&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java (added)
+++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java Mon Jan 25 12:43:22 2010
@@ -0,0 +1,289 @@
+/*
+ * 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.sling.jcr.classloader.internal.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>JCRJarURLConnection</code> extends the
+ * {@link org.apache.sling.jcr.classloader.internal.net.JCRURLConnection} class to support accessing
+ * archive files stored in a JCR Repository.
+ * <p>
+ * Just like the base class, this class requires the URL to resolve, either
+ * directly or through primary item chain, to a repository <code>Property</code>.
+ * <p>
+ * Access to this connections property and archive entry content is perpared
+ * with the {@link #connect()}, which after calling the base class implementation
+ * to find the property tries to find the archive entry and set the connection's
+ * fields according to the entry. This implementation's {@link #connect()}
+ * method fails if the named entry does not exist in the archive.
+ * <p>
+ * The {@link #getInputStream()} method either returns an stream on the archive
+ * entry or on the archive depending on whether an entry path is specified
+ * in the URL or not. Like the base class implementation, this implementation
+ * returns a new <code>InputStream</code> on each invocation.
+ * <p>
+ * If an entry path is defined on the URL, the header fields are set from the
+ * archive entry:
+ * <table border="0" cellspacing="0" cellpadding="3">
+ *  <tr><td><code>Content-Type</code><td>Guessed from the entry name or
+ *      <code>application/octet-stream</code> if the type cannot be guessed
+ *      from the name</tr>
+ *  <tr><td><code>Content-Encoding</code><td><code>null</code></tr>
+ *  <tr><td><code>Content-Length</code><td>The size of the entry</tr>
+ *  <tr><td><code>Last-Modified</code><td>The last modification time of the
+ *      entry</tr>
+ * </table>
+ * <p>
+ * If no entry path is defined on the URL, the header fields are set from the
+ * property by the base class implementation with the exception of the
+ * content type, which is set to <code>application/java-archive</code> by
+ * the {@link #connect()} method.
+ * <p>
+ * <em>Note that this implementation does only support archives stored in the
+ * JCR Repository, no other contained storage such as </em>file<em> or
+ * </em>http<em> is supported.</em>
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ *
+ * @author Felix Meschberger
+ */
+public class JCRJarURLConnection extends JCRURLConnection {
+
+    /** default log category */
+    private static final Logger log =
+        LoggerFactory.getLogger(JCRJarURLConnection.class);
+
+    /**
+     * The name of the MIME content type for this connection's content if
+     * no entry path is defined on the URL (value is "application/java-archive").
+     */
+    protected static final String APPLICATION_JAR = "application/java-archive";
+
+    /**
+     * Creates an instance of this class for the given <code>url</code>
+     * supported by the <code>handler</code>.
+     *
+     * @param url The URL to base the connection on.
+     * @param handler The URL handler supporting the given URL.
+     */
+    JCRJarURLConnection(URL url, JCRJarURLHandler handler) {
+        super(url, handler);
+    }
+
+    /**
+     * Returns the path to the entry contained in the archive or
+     * <code>null</code> if the URL contains no entry specification in the
+     * path.
+     */
+    String getEntryPath() {
+        return getFileParts().getEntryPath();
+    }
+
+    /**
+     * Connects to the URL setting the header fields and preparing for the
+     * {@link #getProperty()} and {@link #getInputStream()} methods.
+     * <p>
+     * After calling the base class implemenation to get the basic connection,
+     * the entry is looked for in the archive to set the content type, content
+     * length and last modification time header fields according to the named
+     * entry. If no entry is defined on the URL, only the content type header
+     * field is set to <code>application/java-archive</code>.
+     * <p>
+     * When this method successfully returns, this connection is considered
+     * connected. In case of an exception thrown, the connection is not
+     * connected.
+     *
+     * @throws IOException if an error occurrs retrieving the data property or
+     *      any of the header field value properties or if any other errors
+     *      occurrs. Any cuasing exception is set as the cause of this
+     *      exception.
+     */
+    public synchronized void connect() throws IOException {
+
+        if (!connected) {
+
+            // have the base class connect to get the jar property
+            super.connect();
+
+            // we assume the connection is now (temporarily) connected,
+            // thus calling the getters will not result in a recursive loop
+            Property property = getProperty();
+            String contentType = getContentType();
+            String contentEncoding = getContentEncoding();
+            int contentLength = getContentLength();
+            long lastModified = getLastModified();
+
+            // mark as not connected to not get false positives if the
+            // following code fails
+            connected = false;
+
+            // Get hold of the data
+            try {
+
+                JarInputStream jins = null;
+                try {
+
+                    // try to get the jar input stream, fail if no jar
+                    jins = new JarInputStream(property.getStream());
+
+                    String entryPath = getEntryPath();
+                    if (entryPath != null) {
+
+                        JarEntry entry = findEntry(jins, entryPath);
+
+                        if (entry != null) {
+
+                            contentType = guessContentTypeFromName(entryPath);
+                            if (contentType == null) {
+                                contentType = APPLICATION_OCTET;
+                            }
+
+                            contentLength = (int) entry.getSize();
+                            lastModified = entry.getTime();
+
+                        } else {
+
+                            throw failure("connect", entryPath +
+                                " not contained in jar archive", null);
+
+                        }
+
+                    } else {
+
+                        // replaces the base class defined content type
+                        contentType = APPLICATION_JAR;
+
+                    }
+
+                } finally {
+                    if (jins != null) {
+                        try {
+                            jins.close();
+                        } catch (IOException ignore) {
+                        }
+                    }
+                }
+
+                log.debug("connect: Using atom '" + property.getPath()
+                    + "' with content type '" + contentType + "' for "
+                    + String.valueOf(contentLength) + " bytes");
+
+                // set the fields
+                setContentType(contentType);
+                setContentEncoding(contentEncoding);
+                setContentLength(contentLength);
+                setLastModified(lastModified);
+
+                // mark connection open
+                connected = true;
+
+            } catch (RepositoryException re) {
+
+                throw failure("connect", re.toString(), re);
+
+            }
+        }
+    }
+
+    /**
+     * Returns an input stream that reads from this open connection. If not
+     * entry path is specified in the URL, this method returns the input stream
+     * providing access to the archive as a whole. Otherwise the input stream
+     * returned is a <code>JarInputStream</code> positioned at the start of
+     * the named entry.
+     * <p>
+     * <b>NOTES:</b>
+     * <ul>
+     * <li>Each call to this method returns a new <code>InputStream</code>.
+     * <li>Do not forget to close the return stream when not used anymore for
+     *      the system to be able to free resources.
+     * </ul>
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return The <code>InputStream</code> on the archive or the entry if
+     *      specified.
+     *
+     * @throws IOException if an error occurrs opening the connection through
+     *      {@link #connect()} or creating the <code>InputStream</code> on the
+     *      repository <code>Property</code>.
+     */
+    public InputStream getInputStream() throws IOException {
+
+        // get the input stream on the archive itself - also enforces connect()
+        InputStream ins = super.getInputStream();
+
+        // access the entry in the archive if defined
+        String entryPath = getEntryPath();
+        if (entryPath != null) {
+            // open the jar input stream
+            JarInputStream jins = new JarInputStream(ins);
+
+            // position at the correct entry
+            findEntry(jins, entryPath);
+
+            // return the input stream
+            return jins;
+        }
+
+        // otherwise just return the stream on the archive
+        return ins;
+    }
+
+    //----------- internal helper to find the entry ------------------------
+
+    /**
+     * Returns the <code>JarEntry</code> for the path from the
+     * <code>JarInputStream</code> or <code>null</code> if the path cannot
+     * be found in the archive.
+     *
+     * @param zins The <code>JarInputStream</code> to search in.
+     * @param path The path of the <code>JarEntry</code> to return.
+     *
+     * @return The <code>JarEntry</code> for the path or <code>null</code>
+     *      if no such entry can be found.
+     *
+     * @throws IOException if a problem occurrs reading from the stream.
+     */
+    static JarEntry findEntry(JarInputStream zins, String path)
+        throws IOException {
+
+        JarEntry entry = zins.getNextJarEntry();
+        while (entry != null) {
+            if (path.equals(entry.getName())) {
+                return entry;
+            }
+
+            entry = zins.getNextJarEntry();
+        }
+        // invariant : nothing found in the zip matching the path
+
+        return null;
+    }
+}

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java?rev=902794&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java (added)
+++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java Mon Jan 25 12:43:22 2010
@@ -0,0 +1,298 @@
+/*
+ * 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.sling.jcr.classloader.internal.net;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javax.jcr.Session;
+
+/**
+ * The <code>JCRJarURLHandler</code> is the <code>URLStreamHandler</code> for
+ * Java Archive URLs for archives from a JCR Repository URLs (JCRJar URL). The
+ * scheme for such ULRs will be <code>jar</code> while the file part of the URL
+ * has the scheme <code>jcr</code>.
+ * <p>
+ * JCRJar URLs have not been standardized yet and may only be created in the
+ * context of an existing <code>Session</code>. Therefore this handler is not
+ * globally available and JCR Repository URLs may only be created through the
+ * factory methods in the {@link org.apache.sling.jcr.classloader.internal.net.URLFactory} class.
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ *
+ * @author Felix Meschberger
+ *
+ * @see org.apache.sling.jcr.classloader.internal.net.JCRJarURLConnection
+ * @see org.apache.sling.jcr.classloader.internal.net.URLFactory
+ * @see org.apache.sling.jcr.classloader.internal.net.URLFactory#createJarURL(Session, String, String)
+ */
+class JCRJarURLHandler extends JCRURLHandler {
+
+    /**
+     * Creates an instance of this handler class.
+     *
+     * @param session The <code>Session</code> supporting this handler. This
+     *      must not be <code>null</code>.
+     *
+     * @throws NullPointerException if <code>session</code> is <code>null</code>.
+     */
+    JCRJarURLHandler(Session session) {
+        super(session);
+    }
+
+    //---------- URLStreamHandler abstracts ------------------------------------
+
+    /**
+     * Gets a connection object to connect to an JCRJar URL.
+     *
+     * @param url The JCRJar URL to connect to.
+     *
+     * @return An instance of the {@link JCRJarURLConnection} class.
+     *
+     * @see JCRJarURLConnection
+     */
+    protected URLConnection openConnection(URL url) {
+        return new JCRJarURLConnection(url, this);
+    }
+
+    /**
+     * Parses the string representation of a <code>URL</code> into a
+     * <code>URL</code> object.
+     * <p>
+     * If there is any inherited context, then it has already been copied into
+     * the <code>URL</code> argument.
+     * <p>
+     * The <code>parseURL</code> method of <code>URLStreamHandler</code>
+     * parses the string representation as if it were an <code>http</code>
+     * specification. Most URL protocol families have a similar parsing. A
+     * stream protocol handler for a protocol that has a different syntax must
+     * override this routine.
+     *
+     * @param url the <code>URL</code> to receive the result of parsing the
+     *            spec.
+     * @param spec the <code>String</code> representing the URL that must be
+     *            parsed.
+     * @param start the character index at which to begin parsing. This is just
+     *            past the '<code>:</code>' (if there is one) that specifies
+     *            the determination of the protocol name.
+     * @param limit the character position to stop parsing at. This is the end
+     *            of the string or the position of the "<code>#</code>"
+     *            character, if present. All information after the sharp sign
+     *            indicates an anchor.
+     */
+    protected void parseURL(URL url, String spec, int start, int limit) {
+        // protected void parseURL(URL url, String s, int i, int j)
+
+        String file = null;
+        String ref = null;
+
+        // split the reference and file part
+        int hash = spec.indexOf('#', limit);
+        boolean emptyFile = hash == start;
+        if (hash > -1) {
+            ref = spec.substring(hash + 1, spec.length());
+            if (emptyFile) {
+                file = url.getFile();
+            }
+        }
+
+        boolean isSpecAbsolute = spec.substring(0, 4).equalsIgnoreCase("jar:");
+        spec = spec.substring(start, limit);
+
+        if (isSpecAbsolute) {
+
+            // get the file part from the absolute spec
+            file = parseAbsoluteSpec(spec);
+
+        } else if (!emptyFile) {
+
+            // build the file part from the url and relative spec
+            file = parseContextSpec(url, spec);
+
+            // split archive and entry names
+            int bangSlash = indexOfBangSlash(file);
+            String archive = file.substring(0, bangSlash);
+            String entry = file.substring(bangSlash);
+
+            // collapse /../, /./ and //
+            entry = canonizeString(entry);
+
+            file = archive + entry;
+
+        }
+
+        setURL(url, "jar", "", -1, null, null, file, null, ref);
+    }
+
+    //---------- internal -----------------------------------------------------
+
+    /**
+     * Finds the position of the bang slash (!/) in the file part of the URL.
+     */
+    static int indexOfBangSlash(String file) {
+
+        for (int i = file.length(); (i = file.lastIndexOf('!', i)) != -1; i--) {
+            if (i != file.length() - 1 && file.charAt(i + 1) == '/') {
+                return i + 1;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Parses the URL spec and checks whether it contains a bang slash and
+     * whether it would get a valid URL. Returns the same value if everything is
+     * fine else a <code>NullPointerException</code> is thrown.
+     *
+     * @param spec The URL specification to check.
+     * @return The <code>spec</code> if everything is ok.
+     * @throws NullPointerException if either no bang slash is contained in the
+     *             spec or if the spec without the bang slash part would not be
+     *             a valid URL.
+     */
+    private String parseAbsoluteSpec(String spec) {
+
+        // find and check bang slash
+        int bangSlash = indexOfBangSlash(spec);
+        if (bangSlash == -1) {
+            throw new NullPointerException("no !/ in spec");
+        }
+
+        try {
+
+            String testSpec = spec.substring(0, bangSlash - 1);
+            URI uri = new URI(testSpec);
+
+            // verify the scheme is the JCR Repository Scheme
+            if (!URLFactory.REPOSITORY_SCHEME.equals(uri.getScheme())) {
+                throw new URISyntaxException(testSpec,
+                    "Unsupported Scheme " + uri.getScheme(), 0);
+            }
+
+        } catch (URISyntaxException use) {
+
+            throw new NullPointerException("invalid url: " + spec + " (" + use
+                + ")");
+
+        }
+
+        return spec;
+    }
+
+    /**
+     * Merges the specification and the file part of the URL respecting the bang
+     * slashes. If the specification starts with a slash, it is regarded as a
+     * complete path of a archive entry and replaces an existing archive entry
+     * specification in the url. Examples :<br>
+     * <table>
+     * <tr>
+     * <th align="left">file
+     * <th align="left">spec
+     * <th align="left">result
+     * <tr>
+     * <td>/some/file/path.jar!/
+     * <td>/some/entry/path
+     * <td>/some/file/path.jar!/some/entry/path
+     * <tr>
+     * <td>/some/file/path.jar!/some/default
+     * <td>/some/entry/path
+     * <td>/some/file/path.jar!/some/entry/path </table>
+     * <p>
+     * If the specification is not absolutes it replaces the last file name part
+     * if the file name does not end with a slash. Examples :<br>
+     * <table>
+     * <tr>
+     * <th align="left">file
+     * <th align="left">spec
+     * <th align="left">result
+     * <tr>
+     * <td>/some/file/path.jar!/
+     * <td>/some/entry/path
+     * <td>/some/file/path.jar!/some/entry/path
+     * <tr>
+     * <td>/some/file/path.jar!/some/default
+     * <td>/some/entry/path
+     * <td>/some/file/path.jar!/some/entry/path </table>
+     *
+     * @param url The <code>URL</code> whose file part is used
+     * @param spec The specification to merge with the file part
+     * @throws NullPointerException If the specification starts with a slash and
+     *             the URL does not contain a slash bang or if the specification
+     *             does not start with a slash and the file part of the URL does
+     *             is not an absolute file path.
+     */
+    private String parseContextSpec(URL url, String spec) {
+
+        // spec is relative to this file
+        String file = url.getFile();
+
+        // if the spec is absolute path, it is an absolute entry spec
+        if (spec.startsWith("/")) {
+
+            // assert the bang slash in the original URL
+            int bangSlash = indexOfBangSlash(file);
+            if (bangSlash == -1) {
+                throw new NullPointerException("malformed context url:" + url
+                    + ": no !/");
+            }
+
+            // remove bang slash part from the original file
+            file = file.substring(0, bangSlash);
+        }
+
+        // if the file is not a directory and spec is a relative file path
+        if (!file.endsWith("/") && !spec.startsWith("/")) {
+
+            // find the start of the file name in the url file path
+            int lastSlash = file.lastIndexOf('/');
+            if (lastSlash == -1) {
+                throw new NullPointerException("malformed context url:" + url);
+            }
+
+            // cut off the file name from the URL file path
+            file = file.substring(0, lastSlash + 1);
+        }
+
+        // concat file part and the spec now
+        return file + spec;
+    }
+
+    public String canonizeString(String s) {
+        int i = 0;
+        int k = s.length();
+        while ((i = s.indexOf("/../")) >= 0)
+            if ((k = s.lastIndexOf('/', i - 1)) >= 0)
+                s = s.substring(0, k) + s.substring(i + 3);
+            else
+                s = s.substring(i + 3);
+        while ((i = s.indexOf("/./")) >= 0)
+            s = s.substring(0, i) + s.substring(i + 2);
+        while (s.endsWith("/..")) {
+            int j = s.indexOf("/..");
+            int l;
+            if ((l = s.lastIndexOf('/', j - 1)) >= 0)
+                s = s.substring(0, l + 1);
+            else
+                s = s.substring(0, j);
+        }
+        if (s.endsWith("/.")) s = s.substring(0, s.length() - 1);
+        return s;
+    }
+}

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java?rev=902794&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java (added)
+++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java Mon Jan 25 12:43:22 2010
@@ -0,0 +1,778 @@
+/*
+ * 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.sling.jcr.classloader.internal.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.jcr.classloader.internal.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * The <code>JCRURLConnection</code> is the <code>URLConnection</code>
+ * implementation to access the data addressed by a JCR Repository URL.
+ * <p>
+ * As the primary use of a <code>URLConnection</code> and thus the
+ * <code>JCRURLConnection</code> is to provide access to the content of a
+ * resource identified by the URL, it is the primary task of this class to
+ * identify and access a repository <code>Property</code> based on the URL. This
+ * main task is executed in the {@link #connect()} method.
+ * <p>
+ * Basically the guideposts to access content from a JCR Repository URL are
+ * the following:
+ * <ul>
+ * <li>The URL must ultimately resolve to a repository property to provide
+ *      content.
+ * <li>If the URL itself is the path to a property, that property is used to
+ *      provide the content.
+ * <li>If the URL is a path to a node, either the
+ *      <code>jcr:content/jcr:data</code> or <code>jcr:data</code> property is
+ *      used or the primary item chain starting with this node is followed until
+ *      no further primary items exist. If the final item is a property, that
+ *      property is used to provide the content.
+ * <li>If neither of the above methods resolve to a property, the
+ *      {@link #connect()} fails and access to the content is not possible.
+ * </ul>
+ * <p>
+ * After having connected the property is available through the
+ * {@link #getProperty()} method. Other methods exist to retrieve repository
+ * related information defined when creating the URL: {@link #getSession()} to
+ * retrieve the session of the URL, {@link #getPath()} to retrieve the path
+ * with which the URL was created and {@link #getItem()} to retrieve the item
+ * with which the URL was created. The results of calling {@link #getProperty()}
+ * and {@link #getItem()} will be the same if the URL directly addressed the
+ * property. If the URL addressed the node whose primary item chain ultimately
+ * resolved to the property, the {@link #getItem()} will return the node and
+ * {@link #getProperty()} will return the resolved property.
+ * <p>
+ * A note on the <code>InputStream</code> available from
+ * {@link #getInputStream()}: Unlike other implementations - for example
+ * for <code>file:</code> or <code>http:</code> URLs - which return the same
+ * stream on each call, this implementation returns a new stream on each
+ * invocation.
+ * <p>
+ * The following header fields are implemented by this class:
+ * <dl>
+ * <dt><code>Content-Length</code>
+ * <dd>The size of the content is filled from the <code>Property.getLength()</code>
+ *      method, which returns the size in bytes of the property's value for
+ *      binary values and the number of characters used for the string
+ *      representation of the value for all other value types.
+ *
+ * <dt><code>Content-Type</code>
+ * <dd>The content type is retrieved from the <code>jcr:mimeType</code>
+ *      property of the property's parent node if existing. Otherwise the
+ *      <code>guessContentTypeFromName</code> method is called on the
+ *      {@link #getPath() path}. If this does not yield a content type, it is
+ *      set to <code>application/octet-stream</code> for binary properties and
+ *      to <code>text/plain</code> for other types.
+ *
+ * <dt><code>Content-Enconding</code>
+ * <dd>The content encoding is retrieved from the <code>jcr:econding</code>
+ *      property of the property's parent node if existing. Otherwise this
+ *      header field remains undefined (aka <code>null</code>).
+ *
+ * <dt><code>Last-Modified</code>
+ * <dd>The last modified type is retrieved from the <code>jcr:lastModified</code>
+ *      property of the property's parent node if existing. Otherwise the last
+ *      modification time is set to zero.
+ * </dl>
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ */
+public class JCRURLConnection extends URLConnection {
+
+    /** Default logging */
+    private static final Logger log =
+        LoggerFactory.getLogger(JCRURLConnection.class);
+
+    /**
+     * The name of the header containing the content size (value is
+     * "content-length").
+     */
+    protected static final String CONTENT_LENGTH = "content-length";
+
+    /**
+     * The name of the header containing the MIME type of the content (value is
+     * "content-type").
+     */
+    protected static final String CONTENT_TYPE = "content-type";
+
+    /**
+     * The name of the header containing the content encoding (value is
+     * "content-encoding").
+     */
+    protected static final String CONTENT_ENCODING = "content-encoding";
+
+    /**
+     * The name of the header containing the last modification time stamp of
+     * the content (value is "last-modified").
+     */
+    protected static final String LAST_MODIFIED = "last-modified";
+
+    /**
+     * The default content type name for binary properties accessed by this
+     * connection (value is "application/octet-stream").
+     * @see #connect()
+     */
+    protected static final String APPLICATION_OCTET = "application/octet-stream";
+
+    /**
+     * The default content type name for non-binary properties accessed by this
+     * connection (value is "text/plain").
+     * @see #connect()
+     */
+    protected static final String TEXT_PLAIN = "text/plain";
+
+    /**
+     * The handler associated with the URL of this connection. This handler
+     * provides the connection with access to the repository and the item
+     * underlying the URL.
+     */
+    private final JCRURLHandler handler;
+
+    /**
+     * The {@link FileParts} encapsulating the repository name, workspace name,
+     * item path and optional archive entry path contained in the file part
+     * of the URL. This field is set on-demand by the {@link #getFileParts()}
+     * method.
+     *
+     * @see #getFileParts()
+     */
+    private FileParts fileParts;
+
+    /**
+     * The <code>Item</code> addressed by the path of this connection's URL.
+     * This field is set on-demand by the {@link #getItem()} method.
+     *
+     * @see #getItem()
+     */
+    private Item item;
+
+    /**
+     * The <code>Property</code> associated with the URLConnection. The field
+     * is only set after the connection has been successfully opened.
+     *
+     * @see #getProperty()
+     * @see #connect()
+     */
+    private Property property;
+
+    /**
+     * The (guessed) content type of the data. Currently the content type is
+     * guessed based on the path name of the page or the binary attribute of the
+     * atom.
+     * <p>
+     * Implementations are free to decide, how to define the content type. But
+     * they are required to set the type in the {@link #connect(Ticket)}method.
+     *
+     * @see #getContentType()
+     * @see #connect()
+     */
+    private String contentType;
+
+    /**
+     * The (guessed) content encoding of the data. Currently the content type is
+     * guessed based on the path name of the page or the binary attribute of the
+     * atom.
+     * <p>
+     * Implementations are free to decide, how to define the content type. But
+     * they are required to set the type in the {@link #connect(Ticket)}method.
+     *
+     * @see #getContentEncoding()
+     * @see #connect()
+     */
+    private String contentEncoding;
+
+    /**
+     * The content lentgh of the data, which is the size field of the atom
+     * status information of the base atom.
+     * <p>
+     * Implementations are free to decide, how to define the content length. But
+     * they are required to set the type in the {@link #connect(Ticket)}method.
+     *
+     * @see #getContentLength()
+     * @see #connect()
+     */
+    private int contentLength;
+
+    /**
+     * The last modification time in milliseconds since the epoch (1970/01/01)
+     * <p>
+     * Implementations are free to decide, how to define the last modification
+     * time. But they are required to set the type in the
+     * {@link #connect(Ticket)}method.
+     *
+     * @see #getLastModified()
+     * @see #connect()
+     */
+    private long lastModified;
+
+    /**
+     * Creates an instance of this class for the given <code>url</code>
+     * supported by the <code>handler</code>.
+     *
+     * @param url The URL to base the connection on.
+     * @param handler The URL handler supporting the given URL.
+     */
+    JCRURLConnection(URL url, JCRURLHandler handler) {
+        super(url);
+        this.handler = handler;
+    }
+
+    /**
+     * Returns the current session of URL.
+     * <p>
+     * Calling this method does not require this connection being connected.
+     */
+    public Session getSession() {
+        return handler.getSession();
+    }
+
+    /**
+     * Returns the path to the repository item underlying the URL of this
+     * connection.
+     * <p>
+     * Calling this method does not require this connection being connected.
+     */
+    public String getPath() {
+        return getFileParts().getPath();
+    }
+
+    /**
+     * Returns the repository item underlying the URL of this connection
+     * retrieved through the path set on the URL.
+     * <p>
+     * Calling this method does not require this connection being connected.
+     *
+     * @throws IOException If the item has to be retrieved from the repository
+     *      <code>Session</code> of this connection and an error occurrs. The
+     *      cause of the exception will refer to the exception thrown from the
+     *      repository. If the path addresses a non-existing item, the cause
+     *      will be a <code>PathNotFoundException</code>.
+     */
+    public Item getItem() throws IOException {
+        if (item == null) {
+            try {
+                item = getSession().getItem(getPath());
+            } catch (RepositoryException re) {
+                throw failure("getItem", re.toString(), re);
+            }
+        }
+
+        return item;
+    }
+
+    /**
+     * Returns the repository <code>Property</code> providing the contents of
+     * this connection.
+     * <p>
+     * Calling this method forces the connection to be opened by calling the
+     * {@link #connect()} method.
+     *
+     * @throws IOException May be thrown by the {@link #connect()} method called
+     *      by this method.
+     *
+     * @see #connect()
+     */
+    public Property getProperty() throws IOException {
+        // connect to set the property value
+        connect();
+
+        return property;
+    }
+
+    //---------- URLConnection overwrites -------------------------------------
+
+    /**
+     * Connects to the URL setting the header fields and preparing for the
+     * {@link #getProperty()} and {@link #getInputStream()} methods.
+     * <p>
+     * The following algorithm is applied:
+     * <ol>
+     * <li>The repository item is retrieved from the URL's
+     *      <code>URLHandler</code>.
+     * <li>If the item is a node, the <code>getPrimaryItem</code> method is
+     *      called on that node. If the node has no primary item, the connection
+     *      fails.
+     * <li>If the item - either from the handler or after calling
+     *      <code>getPrimaryItem</code> is still a node, this method fails
+     *      because a <code>Property</code> is required for a successfull
+     *      connection.
+     * <li>If the property found above is a multi-valued property, connection
+     *      fails, because multi-valued properties are not currently supported.
+     * <li>The content length header field is set from the property length
+     *      (<code>Property.getLength())</code>).
+     * <li>The header fields for the content type, content encoding and last
+     *      modification time are set from the <code>jcr:mimeType</code>,
+     *      <code>jcr:encoding</code>, and <code>jcr:lastModification</code>
+     *      properties of the property's parent node if existing. Otherwise the
+     *      content encoding field is set to <code>null</code> and the last
+     *      modification time is set to zero. The content type field is guessed
+     *      from the name of the URL item. If the content type cannot be
+     *      guessed, it is set to <code>application/octet-stream</code> if the
+     *      property is of binary type or <code>text/plain</code> otherwise.
+     * </ol>
+     * <p>
+     * When this method successfully returns, this connection is considered
+     * connected. In case of an exception thrown, the connection is not
+     * connected.
+     *
+     * @throws IOException if an error occurrs retrieving the data property or
+     *      any of the header field value properties or if any other errors
+     *      occurrs. Any cuasing exception is set as the cause of this
+     *      exception.
+     */
+    public synchronized void connect() throws IOException {
+        // todo: The ContentBus URL must also contain version information on
+        if (!connected) {
+
+            // Get hold of the data
+            try {
+                // resolve the URLs item to a property
+                Property property = Util.getProperty(getItem());
+                if (property == null) {
+                    throw failure("connect",
+                        "Multivalue property not supported", null);
+                }
+
+                // values to set later
+                String contentType;
+                String contentEncoding = null; // no defined content encoding
+                int contentLength = (int) property.getLength();
+                long lastModified;
+
+                Node parent = property.getParent();
+                if (parent.hasProperty("jcr:lastModified")) {
+                    lastModified = parent.getProperty("jcr:lastModified").getLong();
+                } else {
+                    lastModified = 0;
+                }
+                
+                if (parent.hasProperty("jcr:mimeType")) {
+                    contentType = parent.getProperty("jcr:mimeType").getString();
+                } else {
+                    contentType = guessContentTypeFromName(getItem().getName());
+                    if (contentType == null) {
+                        contentType = (property.getType() == PropertyType.BINARY)
+                                ? APPLICATION_OCTET
+                                : TEXT_PLAIN;
+                    }
+                }
+                
+                if (parent.hasProperty("jcr:encoding")) {
+                    contentEncoding = parent.getProperty("jcr:encoding").getString();
+                } else {
+                    contentEncoding = null;
+                }
+
+                log.debug(
+                    "connect: Using property '{}' with content type '{}' for {} bytes",
+                    new Object[] { property.getPath(), contentType,
+                        new Integer(contentLength) });
+
+                // set the fields
+                setProperty(property);
+                setContentType(contentType);
+                setContentEncoding(contentEncoding);
+                setContentLength(contentLength);
+                setLastModified(lastModified);
+
+                // mark connection open
+                connected = true;
+
+            } catch (RepositoryException re) {
+                throw failure("connect", re.toString(), re);
+            }
+        }
+    }
+
+    /**
+     * Returns an input stream that reads from this open connection.
+     * <p>
+     * <b>NOTES:</b>
+     * <ul>
+     * <li>Each call to this method returns a new <code>InputStream</code>.
+     * <li>Do not forget to close the return stream when not used anymore for
+     *      the system to be able to free resources.
+     * </ul>
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @throws IOException if an error occurrs opening the connection through
+     *      {@link #connect()} or creating the <code>InputStream</code> on the
+     *      repository <code>Property</code>.
+     *
+     * @see #connect()
+     */
+    public InputStream getInputStream() throws IOException {
+        try {
+            return getProperty().getStream();
+        } catch (RepositoryException re) {
+            throw failure("getInputStream", re.toString(), re);
+        }
+    }
+
+    /**
+     * Gets the named header field. This implementation only supports the
+     * Content-Type, Content-Encoding, Content-Length and Last-Modified header
+     * fields. All other names return <code>null</code>.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @param s The name of the header field value to return.
+     *
+     * @return The corresponding value or <code>null</code> if not one of the
+     *      supported fields or the named field's value cannot be retrieved
+     *      from the data source.
+     *
+     * @see #connect()
+     */
+    public String getHeaderField(String s) {
+        try {
+            connect();
+            if (CONTENT_LENGTH.equalsIgnoreCase(s)) {
+                return String.valueOf(contentLength);
+            } else if (CONTENT_TYPE.equalsIgnoreCase(s)) {
+                return contentType;
+            } else if (LAST_MODIFIED.equalsIgnoreCase(s)) {
+                return String.valueOf(lastModified);
+            } else if (CONTENT_ENCODING.equalsIgnoreCase(s)) {
+                return contentEncoding;
+            }
+        } catch (IOException ioe) {
+            log.info("getHeaderField: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the header field with the given index. As with
+     * {@link #getHeaderField(String)} only Content-Length, Content-Type,
+     * Content-Encoding, and Last-Modified are supported. All indexes other
+     * than 0, 1, 2 or 3 will return <code>null</code>.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @param i The index of the header field value to return.
+     *
+     * @return The corresponding value or <code>null</code> if not one of the
+     *      supported fields or the known field's value cannot be retrieved
+     *      from the data source.
+     *
+     * @see #connect()
+     */
+    public String getHeaderField(int i) {
+        try {
+            connect();
+            if (i == 0) {
+                return String.valueOf(contentLength);
+            } else if (i == 1) {
+                return contentType;
+            } else if (i == 2) {
+                return String.valueOf(lastModified);
+            } else if (i == 3) {
+                return contentEncoding;
+            }
+        } catch (IOException ioe) {
+            log.info("getHeaderField: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the name of the header field with the given index. As with
+     * {@link #getHeaderField(String)} only Content-Length, Content-Type,
+     * Content-Encoding and Last-Modified are supported. All indexes other than
+     * 0, 1, 2 or 3 will return <code>null</code>.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @param i The index of the header field name to return.
+     * @return The corresponding name or <code>null</code> if not one of the
+     *         supported fields.
+     *
+     * @see #connect()
+     */
+    public String getHeaderFieldKey(int i) {
+        try {
+            connect();
+            if (i == 0) {
+                return CONTENT_LENGTH;
+            } else if (i == 1) {
+                return CONTENT_TYPE;
+            } else if (i == 2) {
+                return LAST_MODIFIED;
+            } else if (i == 3) {
+                return CONTENT_ENCODING;
+            }
+        } catch (IOException ioe) {
+            log
+                .info("getHeaderFieldKey: Problem connecting: "
+                    + ioe.toString());
+            log.debug("dump", ioe);
+        }
+        return null;
+    }
+
+    /**
+     * Returns an unmodifiable map of all header fields. Each entry is indexed
+     * with a string key naming the field. The entry's value is an unmodifiable
+     * list of the string values of the respective header field.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return An unmodifiable map of header fields and their values. The map
+     *      will be empty if an error occurrs connecting through
+     *      {@link #connect()}.
+     *
+     * @see #connect()
+     */
+    public Map getHeaderFields() {
+        Map fieldMap = new HashMap();
+
+        try {
+            connect();
+            fieldMap.put(CONTENT_LENGTH, toList(String.valueOf(contentLength)));
+            fieldMap.put(CONTENT_TYPE, toList(contentType));
+            fieldMap.put(LAST_MODIFIED, toList(String.valueOf(lastModified)));
+
+            // only include if not null))
+            if (contentEncoding != null) {
+                fieldMap.put(CONTENT_ENCODING, toList(contentEncoding));
+            }
+        } catch (IOException ioe) {
+            log.info("getHeaderFields: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return Collections.unmodifiableMap(fieldMap);
+    }
+
+    /**
+     * Returns the content type of the data as a string. This is just a
+     * perfomance convenience overwrite of the base class implementation.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return The content length of the data or <code>null</code> if the
+     *      content type cannot be derived from the data source.
+     *
+     * @see #connect()
+     */
+    public String getContentType() {
+        try {
+            connect();
+            return contentType;
+        } catch (IOException ioe) {
+            log.info("getContentType: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the content encoding of the data as a string. This is just a
+     * perfomance convenience overwrite of the base class implementation.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return The content encoding of the data or <code>null</code> if the
+     *      content encoding cannot be derived from the data source.
+     *
+     * @see #connect()
+     */
+    public String getContentEncoding() {
+        try {
+            connect();
+            return contentEncoding;
+        } catch (IOException ioe) {
+            log.info("getContentEncoding: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the content length of the data as an number. This is just a
+     * perfomance convenience overwrite of the base class implementation.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return The content length of the data or -1 if the content length cannot
+     *         be derived from the data source.
+     *
+     * @see #connect()
+     */
+    public int getContentLength() {
+        try {
+            connect();
+            return contentLength;
+        } catch (IOException ioe) {
+            log.info("getContentLength: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the value of the <code>last-modified</code> header field. The
+     * result is the number of milliseconds since January 1, 1970 GMT.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return the date the resource referenced by this
+     *         <code>URLConnection</code> was last modified, or -1 if not
+     *         known.
+     *
+     * @see #connect()
+     */
+    public long getLastModified() {
+        try {
+            connect();
+            return lastModified;
+        } catch (IOException ioe) {
+            log.info("getLastModified: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+        return -1;
+    }
+
+    //---------- implementation helpers ----------------------------------------
+
+    /**
+     * Returns the URL handler of the URL of this connection.
+     */
+    protected JCRURLHandler getHandler() {
+        return handler;
+    }
+
+    /**
+     * Returns the {@link FileParts} object which contains the decomposed file
+     * part of this connection's URL.
+     */
+    FileParts getFileParts() {
+        if (fileParts == null) {
+            fileParts = new FileParts(getURL().getFile());
+        }
+
+        return fileParts;
+    }
+
+    /**
+     * @param contentEncoding The contentEncoding to set.
+     */
+    protected void setContentEncoding(String contentEncoding) {
+        this.contentEncoding = contentEncoding;
+    }
+
+    /**
+     * @param contentLength The contentLength to set.
+     */
+    protected void setContentLength(int contentLength) {
+        this.contentLength = contentLength;
+    }
+
+    /**
+     * @param contentType The contentType to set.
+     */
+    protected void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    /**
+     * @param lastModified The lastModified to set.
+     */
+    protected void setLastModified(long lastModified) {
+        this.lastModified = lastModified;
+    }
+
+    /**
+     * @param property The property to set.
+     */
+    protected void setProperty(Property property) {
+        this.property = property;
+    }
+
+    //---------- internal -----------------------------------------------------
+
+    /**
+     * Logs the message and returns an IOException to be thrown by the caller.
+     * The log message contains the caller name, the external URL form and the
+     * message while the IOException is only based on the external URL form and
+     * the message given.
+     *
+     * @param method The method in which the error occurred. This is used for
+     *            logging.
+     * @param message The message to log and set in the exception
+     * @param cause The cause of failure. May be <code>null</code>.
+     *
+     * @return The IOException the caller may throw.
+     */
+    protected IOException failure(String method, String message, Throwable cause) {
+        log.info(method + ": URL: " + url.toExternalForm() + ", Reason: "
+            + message);
+
+        if (cause != null) {
+            log.debug("dump", cause);
+        }
+
+        IOException ioe = new IOException(url.toExternalForm() + ": " + message);
+        ioe.initCause(cause);
+        return ioe;
+    }
+
+    /**
+     * Returns an unmodifiable list containing just the given string value.
+     */
+    private List toList(String value) {
+        String[] values = { value };
+        List valueList = Arrays.asList(values);
+        return Collections.unmodifiableList(valueList);
+    }
+}

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java?rev=902794&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java (added)
+++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java Mon Jan 25 12:43:22 2010
@@ -0,0 +1,147 @@
+/*
+ * 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.sling.jcr.classloader.internal.net;
+
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import javax.jcr.Session;
+
+/**
+ * The <code>JCRURLHandler</code> is the <code>URLStreamHandler</code> for
+ * JCR Repository URLs identified by the scheme <code>jcr</code>.
+ * <p>
+ * JCR Repository URLs have not been standardized yet and may only be created
+ * in the context of an existing <code>Session</code>. Therefore this handler
+ * is not globally available and JCR Repository URLs may only be created through
+ * the factory methods in the {@link org.apache.sling.jcr.classloader.internal.net.URLFactory}
+ * class.
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ *
+ * @author Felix Meschberger
+ *
+ * @see org.apache.sling.jcr.classloader.internal.net.JCRURLConnection
+ * @see org.apache.sling.jcr.classloader.internal.net.URLFactory
+ * @see org.apache.sling.jcr.classloader.internal.net.URLFactory#createURL(Session, String)
+ */
+class JCRURLHandler extends URLStreamHandler {
+
+    /**
+     * The session used to create this handler, which is also used to open
+     * the connection object.
+     *
+     * @see #getSession()
+     */
+    private final Session session;
+
+    /**
+     * Creates a new instance of the <code>JCRURLHandler</code> with the
+     * given session.
+     *
+     * @param session The <code>Session</code> supporting this handler. This
+     *      must not be <code>null</code>.
+     *
+     * @throws NullPointerException if <code>session</code> is <code>null</code>.
+     */
+    JCRURLHandler(Session session) {
+        if (session == null) {
+            throw new NullPointerException("session");
+        }
+
+        this.session = session;
+    }
+
+    /**
+     * Returns the session supporting this handler.
+     */
+    Session getSession() {
+        return session;
+    }
+
+    //---------- URLStreamHandler abstracts ------------------------------------
+
+    /**
+     * Gets a connection object to connect to an JCR Repository URL.
+     *
+     * @param url The JCR Repository URL to connect to.
+     *
+     * @return An instance of the {@link JCRURLConnection} class.
+     *
+     * @see JCRURLConnection
+     */
+    protected URLConnection openConnection(URL url) {
+        return new JCRURLConnection(url, this);
+    }
+
+    /**
+     * Checks the new <code>authority</code> and <code>path</code> before
+     * actually setting the values on the url calling the base class
+     * implementation.
+     * <p>
+     * We check the authority to not have been modified from the original URL,
+     * as the authority is dependent on the repository <code>Session</code> on
+     * which this handler is based and which was used to create the original
+     * URL. Likewise the repository and workspace name parts of the path must
+     * not have changed.
+     *
+     * @param u the URL to modify.
+     * @param protocol the protocol name.
+     * @param host the remote host value for the URL.
+     * @param port the port on the remote machine.
+     * @param authority the authority part for the URL.
+     * @param userInfo the userInfo part of the URL.
+     * @param path the path component of the URL.
+     * @param query the query part for the URL.
+     * @param ref the reference.
+     *
+     * @throws IllegalArgumentException if the authority or the repository name
+     *             or workspace name parts of the path has changed.
+     */
+    protected void setURL(URL u, String protocol, String host, int port,
+        String authority, String userInfo, String path, String query, String ref) {
+
+        // check for authority
+        if (u.getAuthority() != authority) {
+            if (u.getAuthority() == null) {
+                if (authority != null) {
+                    throw new IllegalArgumentException("Authority " +
+                        authority + " not supported by this handler");
+                }
+            } else if (!u.getAuthority().equals(authority)) {
+                throw new IllegalArgumentException("Authority " +
+                    authority + " not supported by this handler");
+            }
+        }
+
+        // check for repository and/or workspace modifications
+        FileParts newParts = new FileParts(path);
+        if (!"_".equals(newParts.getRepository())) {
+            throw new IllegalArgumentException("Repository " +
+                newParts.getRepository() + " not supported by this handler");
+        }
+        if (!session.getWorkspace().getName().equals(newParts.getWorkspace())) {
+            throw new IllegalArgumentException("Workspace " +
+                newParts.getWorkspace() + " not supported by this handler");
+        }
+
+        // finally set the new values on the URL
+        super.setURL(u, protocol, host, port, authority, userInfo, path, query,
+            ref);
+    }
+}

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java?rev=902794&view=auto
==============================================================================
--- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java (added)
+++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java Mon Jan 25 12:43:22 2010
@@ -0,0 +1,99 @@
+/*
+ * 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.sling.jcr.classloader.internal.net;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.jcr.Session;
+
+/**
+ * The <code>URLFactory</code> class provides factory methods for creating
+ * JCR Repository and JCRJar URLs.
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ *
+ * @author Felix Meschberger
+ */
+public final class URLFactory {
+
+    /**
+     * The scheme for JCR Repository URLs (value is "jcr").
+     */
+    public static final String REPOSITORY_SCHEME = "jcr";
+
+    /**
+     * The scheme for JCRJar URLs (value is "jar").
+     */
+    public static final String REPOSITORY_JAR_SCHEME = "jar";
+
+    /** Private default constructor, not to be instantiated */
+    private URLFactory() {
+    }
+
+    /**
+     * Creates a new JCR Repository URL for the given session and item path.
+     *
+     * @param session The repository session providing access to the item.
+     * @param path The absolute path to the item. This must be an absolute
+     *      path with a leading slash character. If this is <code>null</code>
+     *      the root node path - <code>/</code> - is assumed.
+     *
+     * @return The JCR Repository URL
+     *
+     * @throws MalformedURLException If an error occurrs creating the
+     *      <code>URL</code> instance.
+     */
+    public static URL createURL(Session session, String path)
+        throws MalformedURLException {
+
+        return new URL(REPOSITORY_SCHEME, "", -1,
+            new FileParts(session, path, null).toString(),
+            new JCRURLHandler(session));
+    }
+
+    /**
+     * Creates a new JCRJar URL for the given session, archive and entry.
+     *
+     * @param session The repository session providing access to the archive.
+     * @param path The absolute path to the archive. This must either be the
+     *      property containing the archive or an item which resolves to such
+     *      a property through its primary item chain. This must be an absolute
+     *      path with a leading slash character. If this is <code>null</code>
+     *      the root node path - <code>/</code> - is assumed.
+     * @param entry The entry within the archive. If <code>null</code>, the URL
+     *      provides access to the archive itself.
+     *
+     * @return The JCRJar URL
+     *
+     * @throws MalformedURLException If an error occurrs creating the
+     *      <code>URL</code> instance.
+     */
+    public static URL createJarURL(Session session, String path, String entry)
+        throws MalformedURLException {
+
+        JCRJarURLHandler handler = new JCRJarURLHandler(session);
+        String file = createURL(session, path).toExternalForm();
+
+        // append entry spec if not null
+        if (entry != null) {
+            file += "!/" + entry;
+        }
+
+        return new URL(REPOSITORY_JAR_SCHEME, "", -1, file, handler);
+    }
+}

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message