All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.net.JCRURLConnection Maven / Gradle / Ivy

Go to download

The Jackrabbit Repository Classloader component provides support for loading Java classes from content repositories that implement the Content Repository for Java Technology API (JCR). The component is mostly independent of the underlying JCR implementation.

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.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.jackrabbit.classloader.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * The JCRURLConnection is the URLConnection
 * implementation to access the data addressed by a JCR Repository URL.
 * 

* As the primary use of a URLConnection and thus the * JCRURLConnection 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 Property based on the URL. This * main task is executed in the {@link #connect()} method. *

* Basically the guideposts to access content from a JCR Repository URL are * the following: *

    *
  • The URL must ultimately resolve to a repository property to provide * content. *
  • If the URL itself is the path to a property, that property is used to * provide the content. *
  • If the URL is a path to a node, either the * jcr:content/jcr:data or jcr:data 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. *
  • If neither of the above methods resolve to a property, the * {@link #connect()} fails and access to the content is not possible. *
*

* 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. *

* A note on the InputStream available from * {@link #getInputStream()}: Unlike other implementations - for example * for file: or http: URLs - which return the same * stream on each call, this implementation returns a new stream on each * invocation. *

* The following header fields are implemented by this class: *

*
Content-Length *
The size of the content is filled from the Property.getLength() * 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. * *
Content-Type *
The content type is retrieved from the jcr:mimeType * property of the property's parent node if existing. Otherwise the * guessContentTypeFromName method is called on the * {@link #getPath() path}. If this does not yield a content type, it is * set to application/octet-stream for binary properties and * to text/plain for other types. * *
Content-Enconding *
The content encoding is retrieved from the jcr:econding * property of the property's parent node if existing. Otherwise this * header field remains undefined (aka null). * *
Last-Modified *
The last modified type is retrieved from the jcr:lastModified * property of the property's parent node if existing. Otherwise the last * modification time is set to zero. *
*

* 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 Item 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 Property 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. *

* 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. *

* 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. *

* 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) *

* 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 url * supported by the handler. * * @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. *

* 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. *

* 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. *

* Calling this method does not require this connection being connected. * * @throws IOException If the item has to be retrieved from the repository * Session 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 PathNotFoundException. */ 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 Property providing the contents of * this connection. *

* 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. *

* The following algorithm is applied: *

    *
  1. The repository item is retrieved from the URL's * URLHandler. *
  2. If the item is a node, the getPrimaryItem method is * called on that node. If the node has no primary item, the connection * fails. *
  3. If the item - either from the handler or after calling * getPrimaryItem is still a node, this method fails * because a Property is required for a successfull * connection. *
  4. If the property found above is a multi-valued property, connection * fails, because multi-valued properties are not currently supported. *
  5. The content length header field is set from the property length * (Property.getLength())). *
  6. The header fields for the content type, content encoding and last * modification time are set from the jcr:mimeType, * jcr:encoding, and jcr:lastModification * properties of the property's parent node if existing. Otherwise the * content encoding field is set to null 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 application/octet-stream if the * property is of binary type or text/plain otherwise. *
*

* 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. *

* NOTES: *

    *
  • Each call to this method returns a new InputStream. *
  • Do not forget to close the return stream when not used anymore for * the system to be able to free resources. *
*

* 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 InputStream on the * repository Property. * * @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 null. *

* 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 null 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 null. *

* 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 null 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 null. *

* 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 null 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. *

* 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. *

* Calling this method implicitly calls {@link #connect()} to ensure the * connection is open. * * @return The content length of the data or null 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. *

* Calling this method implicitly calls {@link #connect()} to ensure the * connection is open. * * @return The content encoding of the data or null 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. *

* 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 last-modified header field. The * result is the number of milliseconds since January 1, 1970 GMT. *

* Calling this method implicitly calls {@link #connect()} to ensure the * connection is open. * * @return the date the resource referenced by this * URLConnection 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 null. * * @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); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy