Mark Rogers af1a735c98 Merge DM-DM_deployment to HEAD
17716 : Copied alfresco/HEAD to alfresco/BRANCHES/DEV/BRIAN/DM-DM_deployment.
  17721 : Moved alfresco/BRANCHES/DEV/BRIAN/DM-DM_deployment to alfresco/BRANCHES/DEV/DM-DM_deployment.
  17809 : Bare Bones of TransferService.
  17813 : more bones
  17824 : bootstrap of the transfer spaces
  17826 : Added username, path, and a getter for the password.
  17832 : createTransferTarget, getTransferTargets() working.
  17836 : transfer target 
    - duplicate name detection
    - delete transfer target by name
    - get transfer target by name
    - TransferException added along with transfer-service.properties
  17840 : transfer target
   - enable / disable
   - update properties
  17851 : Added the notion of a transmitter for the transfer service, and an HttpClient implementation of it.
  17852 : Added the web script needed for the receiving end of a transfer, along with a command processor for clients to 
check availability of the service and credentials.
  17856 : Added a Mockito-based test for HttpClientTransmitterImpl
  17857 : Corrected javadoc for test class
  17858 : Added test for overriding SSL socket factory
  17859 : Wired up the transfer service to the transfer transmitter.
  17896 : Rework to spring for app:transferDefinitions it's now app:transfer_definitio
    - Add throws clauses to TransferService
    - Bare bones of interface for begin and sendManifest
  17944 : Work in progress on manifest file
  17947 : added parent and child associations to the snapshot.
  17956 : Now has the parent path implemented and introduces the TransferManifestNodeFactory.
  17965 : Added content, MLText and collections.
  17978 : addition of source and target peer associations.
  17982 : Fixing parentNode information.
  18008 : XML Manifest Reader checkpoint. (Still incomplete but lots working)
  18040 : ParentPath is transmitted, fixes for source and target assocs.
  18048 : SAIL-30: Initial commit of functionality for begin, sendManifest, and sendContent parts of the transfer process
  18049 : Fix for parse of Locale type.
  18054 : Added TransferManifestNodeHelper and more tests
  18066 : Work in progress check in
   - Implemented the content chunker
 - sketched out more interfaces on TransferTransmitter.
 - Please note that the chunker is not yet connected to the manifest file, that will come next.
r18069 : Wired up manifest reader to content chunker.
r18089 : Fiest cut of callback interface for review.
r18091 : added hashCode implementation which was missing from ContentData
r18095 : Start of the server-side commit. Note that this is an interim commit - not tested.
r18096 : Initial entry of a ContentData implementation of HttpClient's "Part"
r18155 : Work in progress check in.
    TransferEvent - incomplete
    HttpClientTransmitter - first cut complete (not tested)
    Server side - first cut complete (not tested)
  18156 : TransferMessage missing from last check in.
  18166 : check in command processors
  18167 : Work primarily on the transfer commit operation
  18170 : corrected spring errors.
  18176 : Further testing and fixing of transfer commit
  18206 : Work in progress.
  18236 : Work in progress - generally adding debug logging and sorting out exception handlers.
  18240 : Fix to call "end" correctly after exception is thrown with "commit"
  18242 : Aligning the manifest part names.
  18243 : PostSnapshot calls the correct method on the receiver service.
  18267 : First node has transferred.
  18274 : Fixing abort to call end, debug statements, formatting code brackest
  18275 : First code to handle updates. Also improved error messages passed back to client
  18289 : Checked in work in progress.  Content upload not working.
  18290 : Update to ensure file type.
  18300 : Added more log output and some of the error messages.
  18301 : Work in progress
  18302 : Added log output
  18307 : Added a noddy transfer action
  18315 : Sprint 2 complete - transfer and update one node.
  18354 : Now the manifest file has deleted nodes.
 - Adding copyright headers
  18384 : Plumbing for unit tests on one box.
  18416 : First end to end unit test working.   (one node create and update)
  18421 : Added path based update test and many send test.
  18458 : Added the functionality to transfer deleted and restored nodes.
  18481 : Implementation of transferAsync
  18491 : SAIL-32, SAIL-35
   - Added node crawler for DM-DM Transfer F6 and F9.
  18620 : Basic transfer report implementation

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@18858 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2010-02-25 20:07:09 +00:00

514 lines
17 KiB
Java

/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.service.namespace;
import java.io.Serializable;
import java.util.Collection;
import org.alfresco.repo.domain.qname.NamespaceEntity;
/**
* <code>QName</code> represents the qualified name of a Repository item. Each
* QName consists of a local name qualified by a namespace.
* <p>
* The {@link org.alfresco.service.namespace.QNamePattern QNamePattern} is implemented
* to allow instances of this class to be used for direct pattern matching where
* required on interfaces.
*
* @author David Caruana
*
*/
public final class QName implements QNamePattern, Serializable, Cloneable, Comparable<QName>
{
private static final long serialVersionUID = 3977016258204348976L;
private final String namespaceURI; // never null
private final String localName; // never null
private int hashCode;
private final String prefix;
public static final char NAMESPACE_PREFIX = ':';
public static final char NAMESPACE_BEGIN = '{';
public static final char NAMESPACE_END = '}';
public static final int MAX_LENGTH = 100;
/**
* Create a QName
*
* (With no prefix)
*
* @param namespaceURI the qualifying namespace (maybe null or empty string)
* @param localName the local name
* @return the QName
*/
public static QName createQName(String namespaceURI, String localName)
throws InvalidQNameException
{
if (localName == null || localName.length() == 0)
{
throw new InvalidQNameException("A QName must consist of a local name");
}
return new QName(namespaceURI, localName, null);
}
/**
* Create a QName
*
* @param prefix namespace prefix (maybe null or empty string)
* @param localName local name
* @param prefixResolver lookup to resolve mappings between prefix and namespace
* @return the QName
*/
public static QName createQName(String prefix, String localName, NamespacePrefixResolver prefixResolver)
throws InvalidQNameException, NamespaceException
{
// Validate Arguments
if (localName == null || localName.length() == 0)
{
throw new InvalidQNameException("A QName must consist of a local name");
}
if (prefixResolver == null)
{
throw new IllegalArgumentException("A Prefix Resolver must be specified");
}
if (prefix == null)
{
prefix = NamespaceService.DEFAULT_PREFIX;
}
// Calculate namespace URI and create QName
String uri = prefixResolver.getNamespaceURI(prefix);
if (uri == null)
{
throw new NamespaceException("Namespace prefix " + prefix + " is not mapped to a namespace URI");
}
return new QName(uri, localName, prefix);
}
/**
* Create a QName (from prefix format) <code>prefix:localName</code>
*
* @param qname qualified name of the following format <code>prefix:localName</code>
* @param prefixResolver lookup to resolve mappings between prefix and namespace
* @return the QName
*/
public static QName createQName(String qname, NamespacePrefixResolver prefixResolver)
throws InvalidQNameException, NamespaceException
{
QName name = null;
if (qname != null)
{
int colonIndex = qname.indexOf(NAMESPACE_PREFIX);
String prefix = (colonIndex == -1) ? NamespaceService.DEFAULT_PREFIX : qname.substring(0, colonIndex);
String localName = (colonIndex == -1) ? qname : qname.substring(colonIndex +1);
name = createQName(prefix, localName, prefixResolver);
}
return name;
}
/**
* Create a QName from its internal string representation of the following format:
*
* <code>{namespaceURI}localName</code>
*
* @param qname the string representation of the QName
* @return the QName
* @throws IllegalArgumentException
* @throws InvalidQNameException
*/
public static QName createQName(String qname)
throws InvalidQNameException
{
if (qname == null || qname.length() == 0)
{
throw new InvalidQNameException("Argument qname is mandatory");
}
String namespaceURI = null;
String localName = null;
// Parse namespace
int namespaceBegin = qname.indexOf(NAMESPACE_BEGIN);
int namespaceEnd = -1;
if (namespaceBegin != -1)
{
if (namespaceBegin != 0)
{
throw new InvalidQNameException("QName '" + qname + "' must start with a namespaceURI");
}
namespaceEnd = qname.indexOf(NAMESPACE_END, namespaceBegin + 1);
if (namespaceEnd == -1)
{
throw new InvalidQNameException("QName '" + qname + "' is missing the closing namespace " + NAMESPACE_END + " token");
}
namespaceURI = qname.substring(namespaceBegin + 1, namespaceEnd);
}
// Parse name
localName = qname.substring(namespaceEnd + 1);
if (localName == null || localName.length() == 0)
{
throw new InvalidQNameException("QName '" + qname + "' must consist of a local name");
}
// Construct QName
return new QName(namespaceURI, localName, null);
}
/**
* Create a valid local name from the specified name
*
* @param name name to create valid local name from
* @return valid local name
*/
public static String createValidLocalName(String name)
{
// Validate length
if (name == null || name.length() == 0)
{
throw new IllegalArgumentException("Local name cannot be null or empty.");
}
if (name.length() > MAX_LENGTH)
{
name = name.substring(0, MAX_LENGTH);
}
return name;
}
/**
* Create a QName
*
* @param qname qualified name of the following format <code>prefix:localName</code>
* @return string array where index 0 => prefix and index 1 => local name
*/
public static String[] splitPrefixedQName(String qname)
throws InvalidQNameException, NamespaceException
{
if (qname != null)
{
int colonIndex = qname.indexOf(NAMESPACE_PREFIX);
String prefix = (colonIndex == -1) ? NamespaceService.DEFAULT_PREFIX : qname.substring(0, colonIndex);
String localName = (colonIndex == -1) ? qname : qname.substring(colonIndex +1);
return new String[] { prefix, localName };
}
return null;
}
/**
* Construct QName
*
* @param namespace qualifying namespace (maybe null or empty string)
* @param name qualified name
* @param prefix prefix (maybe null or empty string)
*/
private QName(String namespace, String name, String prefix)
{
this.namespaceURI = ((namespace == null) || (namespace.equals(NamespaceEntity.EMPTY_URI_SUBSTITUTE))) ? NamespaceService.DEFAULT_URI : namespace;
this.prefix = prefix;
this.localName = name;
this.hashCode = 0;
}
@Override
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
/**
* Gets the name
*
* @return the name
*/
public String getLocalName()
{
return this.localName;
}
/**
* Gets the namespace
*
* @return the namespace (empty string when not specified, but never null)
*/
public String getNamespaceURI()
{
return this.namespaceURI;
}
/**
* Gets a prefix resolved version of this QName
*
* @param resolver namespace prefix resolver
* @return QName with prefix resolved
*/
public QName getPrefixedQName(NamespacePrefixResolver resolver)
{
Collection<String> prefixes = resolver.getPrefixes(namespaceURI);
if (prefixes.size() == 0)
{
throw new NamespaceException("A namespace prefix is not registered for uri " + namespaceURI);
}
String resolvedPrefix = prefixes.iterator().next();
if (prefix != null && prefix.equals(resolvedPrefix))
{
return this;
}
return new QName(namespaceURI, localName, resolvedPrefix);
}
/**
* Two QNames are equal only when both their name and namespace match.
*
* Note: The prefix is ignored during the comparison.
*/
public boolean equals(Object object)
{
if (this == object)
{
return true;
}
if (object instanceof QName)
{
QName other = (QName)object;
// namespaceURI and localname are not allowed to be null
return (this.localName.equals(other.localName) &&
this.namespaceURI.equals(other.namespaceURI));
}
return false;
}
/**
* Performs a direct comparison between qnames.
*
* @see #equals(Object)
*/
public boolean isMatch(QName qname)
{
return this.equals(qname);
}
/**
* Calculate hashCode. Follows pattern used by String where hashCode is
* cached (QName is immutable).
*/
public int hashCode()
{
if (this.hashCode == 0)
{
// the hashcode assignment is atomic - it is only an integer
this.hashCode = ((37 * localName.hashCode()) + namespaceURI.hashCode());
}
return this.hashCode;
}
/**
* Render string representation of QName using format:
*
* <code>{namespace}name</code>
*
* @return the string representation
*/
public String toString()
{
return new StringBuilder(80).append(NAMESPACE_BEGIN)
.append(namespaceURI)
.append(NAMESPACE_END)
.append(localName).toString();
}
/**
* Uses the {@link #getNamespaceURI() namespace URI} and then the {@link #getLocalName() localname}
* to do the comparison i.e. the comparison is alphabetical.
*/
public int compareTo(QName qname)
{
int namespaceComparison = this.namespaceURI.compareTo(qname.namespaceURI);
if (namespaceComparison != 0)
{
return namespaceComparison;
}
// Namespaces are the same. Do comparison on localname
return this.localName.compareTo(qname.localName);
}
/**
* Render string representation of QName using format:
*
* <code>prefix:name</code>
*
* @return the string representation
*/
public String toPrefixString()
{
return (prefix == null) ? localName : prefix + NAMESPACE_PREFIX + localName;
}
/**
* Getter version of toPrefixString()
*
* @return the string representation of QName
*/
public String getPrefixString()
{
return toPrefixString();
}
/**
* Render string representation of QName using format:
*
* <code>prefix:name</code>
*
* according to namespace prefix mappings of specified namespace resolver.
*
* @param prefixResolver namespace prefix resolver
*
* @return the string representation
*/
public String toPrefixString(NamespacePrefixResolver prefixResolver)
{
Collection<String> prefixes = prefixResolver.getPrefixes(namespaceURI);
if (prefixes.size() == 0)
{
throw new NamespaceException("A namespace prefix is not registered for uri " + namespaceURI);
}
String prefix = prefixes.iterator().next();
if (prefix.equals(NamespaceService.DEFAULT_PREFIX))
{
return localName;
}
else
{
return prefix + NAMESPACE_PREFIX + localName;
}
}
/**
* Creates a QName representation for the given String. If the String has no namespace the Alfresco namespace is
* added. If the String has a prefix an attempt to resolve the prefix to the full URI will be made.
*
* @param str The string to convert
* @return A QName representation of the given string
*/
public static QName resolveToQName(NamespacePrefixResolver prefixResolver, String str)
{
QName qname = null;
if (str == null || str.length() == 0)
{
throw new IllegalArgumentException("str parameter is mandatory");
}
if (str.charAt(0) == (NAMESPACE_BEGIN))
{
// create QName directly
qname = createQName(str);
}
else if (str.indexOf(NAMESPACE_PREFIX) != -1)
{
// extract the prefix and try and resolve using the
// namespace service
int end = str.indexOf(NAMESPACE_PREFIX);
String prefix = str.substring(0, end);
String localName = str.substring(end + 1);
String uri = prefixResolver.getNamespaceURI(prefix);
if (uri != null)
{
qname = createQName(uri, localName);
}
}
else
{
// there's no namespace so prefix with Alfresco's Content Model
qname = createQName(NamespaceService.CONTENT_MODEL_1_0_URI, str);
}
return qname;
}
/**
* Creates a string representation of a QName for the given string. If the given string already has a namespace,
* either a URL or a prefix, nothing the given string is returned. If it does not have a namespace the Alfresco
* namespace is added.
*
* @param str
* The string to convert
*
* @return A QName String representation of the given string
*/
public static String resolveToQNameString(NamespacePrefixResolver prefixResolver, String str)
{
String result = str;
if (str == null || str.length() == 0)
{
throw new IllegalArgumentException("str parameter is mandatory");
}
if (str.charAt(0) != NAMESPACE_BEGIN)
{
if (str.indexOf(NAMESPACE_PREFIX) != -1)
{
// get the prefix and resolve to the uri
int end = str.indexOf(NAMESPACE_PREFIX);
String prefix = str.substring(0, end);
String localName = str.substring(end + 1);
String uri = prefixResolver.getNamespaceURI(prefix);
if (uri != null)
{
result = new StringBuilder(64).append(NAMESPACE_BEGIN).append(uri).append(NAMESPACE_END)
.append(localName).toString();
}
}
else
{
// there's no namespace so prefix with Alfresco's Content Model
result = new StringBuilder(64).append(NAMESPACE_BEGIN).append(NamespaceService.CONTENT_MODEL_1_0_URI)
.append(NAMESPACE_END).append(str).toString();
}
}
return result;
}
}