blob: ab9f9e163230abe0fb3911da8f5fd0a43fb450db [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.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() {
StringBuffer buf = new StringBuffer();
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 + "'");
}
}