/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . * #L% */ package org.alfresco.repo.virtual.ref; import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * A generic, immutable virtualized artefact reference.
* Refers virtualized artefacts through :
* * * @author Bogdan Horje */ public class Reference { private static Log logger = LogFactory.getLog(Reference.class); private static final Character VIRTUAL_TOKEN = 'v'; /** * Quick Reference compliance check of a {@link NodeRef}.
* NodeRef Reference representations validated by this method should produce * valid Reference objects based on the given {@link NodeRef} when passed to * the {@link #fromNodeRef(NodeRef)} method. * * @param nodeRef * @return true if the given {@link NodeRef} is a valid * Reference representation
* false otherwise */ public static final boolean isReference(NodeRef nodeRef) { if (nodeRef != null) { String id = nodeRef.getId(); if ((id != null) && (id.length() > 1) && (id.startsWith("" + VIRTUAL_TOKEN))) { char token = id.charAt(1); Encoding encoding = Encodings.fromToken(token); if (encoding != null) { // // TODO experimental step 2 (sub-optimal) - remove & refactor so that we don't parse twice (for smart folder virtual ids) // try { Reference ref = fromNodeRef(nodeRef); if (ref != null) { return true; } } catch (ReferenceParseException rpe) { // ignore if (logger.isTraceEnabled()) { logger.trace("Ignore parse exception: "+rpe.getMessage()); } } catch (ReferenceEncodingException ree) { // ignore if (logger.isTraceEnabled()) { logger.trace("Ignore encoding exception: "+ree.getMessage()); } } } } if (logger.isTraceEnabled()) { logger.trace("NodeRef is not a reference: "+nodeRef); } } return false; } /** * {@link NodeRef} {@link Reference} representation decoder/converter * method.
* Creates a {@link Reference} representation based on the ID of the given * {@link NodeRef}.
* It expects a {@link #VIRTUAL_TOKEN} prefixed encoded string. The encoded * string must start with a valid {@link Encoding} token. The Reference * representation structure is (no delimiters between the 3 elements): * VIRTUAL_TOKEN ENCODING_TOKEN referenceString Given that a valid encoding * was detected {@link Encoding#urlNative} information is used to obtain a * reference string. The reference string is parsed using the encoding * configured parser. * * @param nodeRef * @return the {@link Reference} object corresponding to the given * {@link NodeRef} * @throws ReferenceParseException * if an error occurs during the reference string parsing * @throws ReferenceEncodingException * if the {@link NodeRef} ID has an invalid virtual token prefix * or it uses an invalid encoding token */ public static final Reference fromNodeRef(NodeRef nodeRef) throws ReferenceParseException, ReferenceEncodingException { String id = nodeRef.getId(); if (id.startsWith("" + VIRTUAL_TOKEN) && (id.length() > 1)) // belts-and-braces { char token = id.charAt(1); Encoding encoding = Encodings.fromToken(token); if (encoding == null) { throw new ReferenceEncodingException("Invalid encoding token " + token + " in " + id); } else { String referenceString = id.substring(2); if (!encoding.urlNative) { referenceString = new String(org.apache.commons.codec.binary.Base64.decodeBase64(referenceString)); } Reference reference = encoding.parser.parse(referenceString); return reference.propagateNodeRefMutations(nodeRef); } } else { throw new ReferenceEncodingException("Invalid node ID format " + id); } } // Average reference length log trace private static long _trace_refLength = 0; private static long _trace_refCount = 0; private static final long _trace_refBatchSize = 4096 * 2; private static synchronized void _trace_avg_ref_length(long refLength) { _trace_refLength += refLength; _trace_refCount++; if (_trace_refBatchSize > 0 && _trace_refCount % _trace_refBatchSize == 0) { logger.trace("Average reference encoding size : " + (_trace_refLength / _trace_refCount)); _trace_refCount = 0; _trace_refLength = 0; } } private Encoding encoding; private Protocol protocol; private Resource resource; private List parameters; /** * Constructor * * @param encoding * the default {@link Encoding} of the new resource - to be used * where an encoding is required and none is specified * @param protocol * @param resource * @param parameters * resource parameters - a copy of the provided list will be * stored by this reference */ public Reference(Encoding encoding, Protocol protocol, Resource resource, List parameters) { this.encoding = encoding; this.protocol = protocol; this.resource = resource; this.parameters = new LinkedList<>(parameters); } public Reference(Encoding encoding, Protocol protocol, Resource resource) { this(encoding, protocol, resource, Collections. emptyList()); } /** * @return a {@link String} representation of this reference using its * default {@link Encoding} * @throws ReferenceEncodingException */ public String encode() throws ReferenceEncodingException { return encode(this.encoding); } /** * @param anEncoding * @return a {@link String} representation of this reference using the given * {@link Encoding} * @throws ReferenceEncodingException */ public String encode(Encoding anEncoding) throws ReferenceEncodingException { return anEncoding.stringifier.stringify(this); } /** * @return the default {@link Encoding} of this reference */ public Encoding getEncoding() { return this.encoding; } public Protocol getProtocol() { return this.protocol; } public Resource getResource() { return this.resource; } public List getParameters() { return parameters; } /** * @return a {@link NodeRef} representation of this resource using the * {@link StoreRef#STORE_REF_WORKSPACE_SPACESSTORE} and the default * encoding of this resource * @throws ReferenceEncodingException */ public NodeRef toNodeRef() throws ReferenceEncodingException { return toNodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); } /** * @param storeRef * @return a {@link NodeRef} representation of this resource using the given * {@link StoreRef} and the default encoding of this resource * @throws ReferenceEncodingException */ public NodeRef toNodeRef(StoreRef storeRef) throws ReferenceEncodingException { return toNodeRef(storeRef, this.encoding); } /** * @param storeRef * @param encoding * @return a {@link NodeRef} representation of this resource using the given * {@link StoreRef} and {@link Encoding} * @throws ReferenceEncodingException */ public NodeRef toNodeRef(StoreRef storeRef, Encoding encoding) throws ReferenceEncodingException { String id = encode(encoding); // TODO: move non-native encoding to encoding object itself if (!encoding.urlNative) { id = new String(org.apache.commons.codec.binary.Base64.encodeBase64(id.getBytes(), false)); } StringBuilder idBuilder = new StringBuilder(); idBuilder.append(VIRTUAL_TOKEN); idBuilder.append(encoding.token); idBuilder.append(id); NodeRef theNode = new NodeRef(storeRef, idBuilder.toString()); if (logger.isTraceEnabled()) { _trace_avg_ref_length(theNode.toString().length()); } return theNode; } /** * Double-dispatches {@link ProtocolMethod}s.
* Uses {@link Protocol#dispatch(ProtocolMethod, Reference)} to trigger * concrete protocol based double dispatch * ProtocolMethod::execute invocation on the given method * object. * * @param method * @return the dispatched method execution result * @throws ProtocolMethodException */ public R execute(ProtocolMethod method) throws ProtocolMethodException { return this.protocol.dispatch(method, this); } /** * Despite claimed {@link NodeRef} opacity Alfresco sometimes alters * NodeRefs representation to achieve functionality. For example see * {@link VersionUtil#convertNodeRef(NodeRef)}.
* We say that altered {@link NodeRef}s have suffered mutations and we try * to detect those mutations and create a correspondent reference. * * @param mutatedNodeRef * @return a mutated version of this {@link Reference} corresponding to the * given mutated node or this Reference if no mutations * are detected */ public Reference propagateNodeRefMutations(NodeRef mutatedNodeRef) { return protocol.propagateNodeRefMutations(mutatedNodeRef, this); } @Override public String toString() { try { return encode(); } catch (ReferenceEncodingException e) { logger.error("Invalid reference", e); return super.toString(); } } @Override public int hashCode() { return resource != null ? resource.hashCode() : 0; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj == null) { return false; } else if (!(obj instanceof Reference)) { return false; } Reference other = (Reference) obj; if (resource == null) { return other.resource == null; } else { if (!this.resource.equals(other.resource)) { return false; } else { if (parameters == null) { return other.parameters == null; } else { return parameters.equals(other.parameters); } } } } }