package fr.acxio.tools.agia.alfresco;
/*
* Copyright 2014 Acxio
*
* Licensed 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.
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import org.alfresco.webservice.repository.RepositoryServiceSoapBindingStub;
import org.alfresco.webservice.types.Query;
import org.alfresco.webservice.types.Reference;
import org.alfresco.webservice.types.ResultSet;
import org.alfresco.webservice.types.ResultSetRow;
import org.alfresco.webservice.util.ISO9075;
import org.alfresco.webservice.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import fr.acxio.tools.agia.alfresco.domain.QName;
import fr.acxio.tools.agia.convert.ConversionException;
import fr.acxio.tools.agia.convert.FormatConverter;
/**
* <p>
* Specific value format converter which converts a value into an Alfresco
* category reference.
* </p>
* <p>
* It takes path or names for categories, using "/" as a path separator.</br> It
* can take either a full path without any specific encoding, or a xpath base
* path and a name.</br>
* </p>
* <p>
* An example of a full path would be:</br>
* {@code /cm:generalclassifiable/cm:Regions/cm:EUROPE/cm:Western Europe/cm:France}
* </br> </br> An example of a base path and a name would be:</br>
* {@code /cm:generalclassifiable/cm:Regions//*}</br> {@code France}</br>
* </p>
* <p>
* The full path or the name are the input value to be formatted.</br> The base
* path, if any, is defined by the attribute {@code basepath}.
* </p>
* <p>
* Note that the path elements can use the short or the long representation of
* {@link fr.acxio.tools.agia.alfresco.domain.QName qualified names}. The
* examples above use the short one.</br> Being able to use both representations
* relies on the namespaceContext.
* </p>
*
* @author pcollardez
*
*/
public class AlfrescoCategoryConverter extends AlfrescoServicesConsumer implements FormatConverter {
private static final String PATH_SPLIT_REGEX = "(/*)((?:\\{[^}]*})?[^/]*)";
private static final Pattern PATH_SPLIT_PATTERN = Pattern.compile(PATH_SPLIT_REGEX);
private static final String FULLPATH_QUERY = "+PATH:\"%s\"";
private static final String BASEPATH_AND_NAME_QUERY = "+TYPE:\"cm:category\" +@cm\\:name:\"%s\" +PATH:\"%s\"";
private static final Logger LOGGER = LoggerFactory.getLogger(AlfrescoCategoryConverter.class);
private NamespaceContext namespaceContext;
private String basePath;
private boolean ignoreUnknown = true;
public String getBasePath() {
return basePath;
}
@CacheEvict(value = "rcategories", allEntries = true)
public void setBasePath(String sBasePath) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Evict all Alfresco categories");
}
basePath = sBasePath;
}
public NamespaceContext getNamespaceContext() {
return namespaceContext;
}
@CacheEvict(value = "rcategories", allEntries = true)
public void setNamespaceContext(NamespaceContext sNamespaceContext) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Evict all Alfresco categories");
}
namespaceContext = sNamespaceContext;
}
public boolean isIgnoreUnknown() {
return ignoreUnknown;
}
public void setIgnoreUnknown(boolean sIgnoreUnknown) {
ignoreUnknown = sIgnoreUnknown;
}
@Cacheable(value = "rcategories")
public List<String> convert(String sSource) throws ConversionException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Calling Alfresco for category: " + sSource);
}
List<String> aResult = (List<String>) (ignoreUnknown ? Collections.emptyList() : Collections.singletonList(sSource));
if ((sSource != null) && sSource.length() > 0) {
try {
init();
String aQuery;
if ((basePath == null) || (basePath.length() == 0)) {
// sSource is a fullpath
aQuery = String.format(FULLPATH_QUERY, encodePath(sSource));
} else {
aQuery = String.format(BASEPATH_AND_NAME_QUERY, sSource, encodePath(basePath));
}
RepositoryServiceSoapBindingStub repositoryService = getAlfrescoService().getRepositoryService();
Query aLQuery = new Query("lucene", aQuery);
ResultSet rs = repositoryService.query(STORE, aLQuery, false).getResultSet();
if (rs.getTotalRowCount() > 0) {
aResult = new ArrayList<String>();
for (ResultSetRow aRSRow : rs.getRows()) {
aResult.add(Utils.getNodeRef(new Reference(STORE, aRSRow.getNode().getId(), null)));
}
} else {
LOGGER.info("Category not found: " + sSource);
}
} catch (Exception e) {
throw new ConversionException("Error retrieving the category: " + sSource, e);
} finally {
cleanup();
}
}
return aResult;
}
/**
* Encode a path according to ISO 9075.</br> Each part of the path is
* encoded by itself, the path separators are preserved.</br> A part of a
* path is a {@link fr.acxio.tools.agia.alfresco.domain.QName QName},
* therefore it can use the short or the long representation.
*
* @param sPath
* a path to encode
* @return the encoded path
*/
private String encodePath(String sPath) {
String aResult = sPath;
if ((sPath != null) && (sPath.length() > 0)) {
StringBuilder aBuilder = new StringBuilder();
Matcher aMatcher = PATH_SPLIT_PATTERN.matcher(sPath);
QName aQName;
String aGroup2;
while (aMatcher.find()) {
if (aMatcher.group(1) != null) {
aBuilder.append(aMatcher.group(1));
}
aGroup2 = aMatcher.group(2);
if ((aGroup2 != null) && (aGroup2.length() > 0)) {
try {
aQName = new QName(aGroup2, namespaceContext);
if (aQName.getPrefix().equals(XMLConstants.DEFAULT_NS_PREFIX) && aQName.getNamespaceURI().equals(XMLConstants.NULL_NS_URI)) {
aBuilder.append(aGroup2); // Not a qname so just
// pass it
} else {
aBuilder.append(aQName.getPrefix()).append(":").append(ISO9075.encode(aQName.getLocalName()));
}
} catch (IllegalArgumentException e) {
aBuilder.append(aGroup2);
}
}
}
aResult = aBuilder.toString();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("The path '" + sPath + "' is encoded as '" + aResult + "'");
}
}
return aResult;
}
}