blob: a5c1172f9218034fe6e93c6309a9f2dec608ea5c [file] [log] [blame]
/*
* 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.sling.ide.impl.vlt.serialization;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import javax.jcr.Credentials;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.nodetype.NodeType;
import org.apache.jackrabbit.vault.fs.api.Aggregate;
import org.apache.jackrabbit.vault.fs.api.Aggregator;
import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
import org.apache.jackrabbit.vault.fs.api.VaultFile;
import org.apache.jackrabbit.vault.fs.api.VaultFileSystem;
import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
import org.apache.jackrabbit.vault.fs.impl.aggregator.FileAggregator;
import org.apache.jackrabbit.vault.fs.impl.aggregator.GenericAggregator;
import org.apache.jackrabbit.vault.fs.impl.io.DocViewSerializer;
import org.apache.jackrabbit.vault.util.Constants;
import org.apache.jackrabbit.vault.util.JcrConstants;
import org.apache.jackrabbit.vault.util.MimeTypes;
import org.apache.jackrabbit.vault.util.PlatformNameFormat;
import org.apache.jackrabbit.vault.util.Text;
import org.apache.sling.ide.impl.vlt.VaultFsLocator;
import org.apache.sling.ide.jcr.RepositoryUtils;
import org.apache.sling.ide.log.Logger;
import org.apache.sling.ide.serialization.SerializationData;
import org.apache.sling.ide.serialization.SerializationDataBuilder;
import org.apache.sling.ide.serialization.SerializationException;
import org.apache.sling.ide.serialization.SerializationKind;
import org.apache.sling.ide.serialization.SerializationKindManager;
import org.apache.sling.ide.transport.ResourceProxy;
public class VltSerializationDataBuilder implements SerializationDataBuilder {
private VaultFsLocator fsLocator;
private SerializationKindManager skm;
private org.apache.sling.ide.transport.Repository repo;
private Session session;
private VaultFileSystem fs;
private Logger logger;
public VltSerializationDataBuilder(Logger logger, VaultFsLocator fsLocator) {
if ( logger == null )
throw new RuntimeException("Logger is null");
if ( fsLocator == null )
throw new RuntimeException("fsLocator is null");
this.logger = logger;
this.fsLocator = fsLocator;
}
public void init(org.apache.sling.ide.transport.Repository repository, File contentSyncRoot)
throws SerializationException {
this.repo = repository;
try {
this.skm = new SerializationKindManager();
this.skm.init(repository);
if (!contentSyncRoot.exists()) {
throw new IllegalArgumentException("contentSyncRoot does not exist: "+contentSyncRoot);
}
Repository jcrRepo = RepositoryUtils.getRepository(repo.getRepositoryInfo());
Credentials credentials = RepositoryUtils.getCredentials(repo.getRepositoryInfo());
session = jcrRepo.login(credentials);
RepositoryAddress address = RepositoryUtils.getRepositoryAddress(repo.getRepositoryInfo());
fs = fsLocator.getFileSystem(address, contentSyncRoot, session);
} catch (org.apache.sling.ide.transport.RepositoryException e) {
throw new SerializationException(e);
} catch (RepositoryException e) {
throw new SerializationException(e);
} catch (IOException e) {
throw new SerializationException(e);
} catch (ConfigurationException e) {
throw new SerializationException(e);
}
}
@Override
public void destroy() {
if (session != null) {
session.logout();
}
}
@Override
public SerializationData buildSerializationData(File contentSyncRoot, ResourceProxy resource) throws SerializationException {
try {
List<Aggregate> chain = findAggregateChain(resource);
if (chain == null) {
return null;
}
Aggregate aggregate = chain.get(chain.size() - 1);
String fileOrFolderPathHint = calculateFileOrFolderPathHint(chain);
String nameHint = PlatformNameFormat.getPlatformName(aggregate.getName());
SerializationKind serializationKind = getSerializationKind(aggregate);
if (resource.getPath().equals("/") || serializationKind == SerializationKind.METADATA_PARTIAL
|| serializationKind == SerializationKind.FILE || serializationKind == SerializationKind.FOLDER) {
nameHint = Constants.DOT_CONTENT_XML;
} else if (serializationKind == SerializationKind.METADATA_FULL) {
nameHint += ".xml";
}
logger.trace("Got location {0} for path {1}", fileOrFolderPathHint, resource.getPath());
if (!needsDir(aggregate)) {
return SerializationData.empty(fileOrFolderPathHint, serializationKind);
}
DocViewSerializer s = new DocViewSerializer(aggregate);
ByteArrayOutputStream out = new ByteArrayOutputStream();
s.writeContent(out);
byte[] result = out.toByteArray();
return new SerializationData(fileOrFolderPathHint, nameHint, result, serializationKind);
} catch (RepositoryException e) {
throw new SerializationException(e);
} catch (IOException e) {
throw new SerializationException(e);
}
}
private SerializationKind getSerializationKind(Aggregate aggregate) throws RepositoryException {
NodeType[] mixinNodeTypes = aggregate.getNode().getMixinNodeTypes();
List<String> mixinNodeTypeNames = new ArrayList<>(mixinNodeTypes.length);
for (NodeType nodeType : mixinNodeTypes)
mixinNodeTypeNames.add(nodeType.getName());
return skm.getSerializationKind(aggregate.getNode()
.getPrimaryNodeType()
.getName(), mixinNodeTypeNames);
}
private boolean needsDir(Aggregate aggregate) throws RepositoryException, PathNotFoundException,
ValueFormatException {
Aggregator aggregator = fs.getAggregateManager().getAggregator(aggregate.getNode(), null);
boolean needsDir = true;
if (aggregator instanceof FileAggregator) {
needsDir = false;
// TODO - copy-pasted from FileAggregator, and really does not belong here...
Node content = aggregate.getNode();
if (content.isNodeType(JcrConstants.NT_FILE)) {
content = content.getNode(JcrConstants.JCR_CONTENT);
}
String mimeType = null;
if (content.hasProperty(JcrConstants.JCR_MIMETYPE)) {
try {
mimeType = content.getProperty(JcrConstants.JCR_MIMETYPE).getString();
} catch (RepositoryException e) {
// ignore
}
}
if (mimeType == null) {
// guess mime type from name
mimeType = MimeTypes.getMimeType(aggregate.getNode().getName(),
MimeTypes.APPLICATION_OCTET_STREAM);
}
needsDir = !MimeTypes.matches(aggregate.getNode().getName(), mimeType,
MimeTypes.APPLICATION_OCTET_STREAM);
if (!needsDir) {
if (content.hasProperty(JcrConstants.JCR_MIXINTYPES)) {
for (Value v : content.getProperty(JcrConstants.JCR_MIXINTYPES).getValues()) {
if (!v.getString().equals(JcrConstants.MIX_LOCKABLE)) {
needsDir = true;
break;
}
}
}
}
// TODO - copy-pasted from GenericAggregator
} else if (aggregator instanceof GenericAggregator) {
if (isPlainNtFolder(aggregate)) {
needsDir = false;
}
}
return needsDir;
}
private String calculateFileOrFolderPathHint(List<Aggregate> chain) throws RepositoryException {
ListIterator<Aggregate> aggs = chain.listIterator();
StringBuilder out = new StringBuilder();
while (aggs.hasNext()) {
Aggregate cur = aggs.next();
if (aggs.previousIndex() == 0) {
out.append(PlatformNameFormat.getPlatformPath(cur.getPath()));
} else {
out.append("/");
out.append(PlatformNameFormat.getPlatformPath(cur.getRelPath()));
}
if (needsDir(cur)) {
SerializationKind serializationKind = getSerializationKind(cur);
if (serializationKind == SerializationKind.FILE) {
out.append(".dir");
}
if (!aggs.hasNext() && serializationKind == SerializationKind.METADATA_FULL) {
out.delete(out.lastIndexOf("/"), out.length());
}
}
}
return out.toString();
}
private boolean isPlainNtFolder(Aggregate agg) throws RepositoryException {
return agg.getNode().getPrimaryNodeType().getName().equals("nt:folder")
&& agg.getNode().getMixinNodeTypes().length == 0;
}
/**
* Returns the aggregates for a specific resource
*
* <p>
* In the simplest case, a single element is returned in the chain, signalling that the aggregate is a top-level
* one.
* </p>
*
* <p>
* For leaf aggregates, the list contains the top-most aggregates first and ends up with the leaf-most ones.
* </p>
*
* @param resource the resource to find the aggregate chain for
* @return a list of aggregates
* @throws IOException
* @throws RepositoryException
*/
private List<Aggregate> findAggregateChain(ResourceProxy resource) throws IOException, RepositoryException {
VaultFile vaultFile = fs.getFile(PlatformNameFormat.getPlatformPath(resource.getPath()));
if (vaultFile == null || vaultFile.getAggregate() == null) {
// this file might be a leaf aggregate of a vaultfile higher in the resource path ; so look for a
// parent higher
String parentPath = Text.getRelativeParent(resource.getPath(), 1);
while (!parentPath.equals("/")) {
VaultFile parentFile = fs.getFile(PlatformNameFormat.getPlatformPath(parentPath));
if (parentFile != null) {
Aggregate parentAggregate = parentFile.getAggregate();
ArrayList<Aggregate> parents = new ArrayList<>();
parents.add(parentAggregate);
List<Aggregate> chain = lookForAggregateInLeaves(resource, parentAggregate, parents);
if (chain != null) {
return chain;
}
}
parentPath = Text.getRelativeParent(parentPath, 1);
}
return null;
}
return Collections.singletonList(vaultFile.getAggregate());
}
/**
* Recursively looks for an aggregate matching the <tt>resource</tt>'s path starting at the <tt>parentAggregate</tt>
*
* <p>
* The returned chain will contain at least one aggregate, in case the resource is contained in a stand-alone (?)
* aggregate, or multiple aggregates in case the matching aggregate is a leaf one.
* </p>
*
* @param resource the resource
* @param parentAggregate the known parent aggregate which potentially matches this resource
* @param chain the chain used to record all intermediate aggregates
* @return the final aggregate chain
*
* @throws RepositoryException
*/
private List<Aggregate> lookForAggregateInLeaves(ResourceProxy resource, Aggregate parentAggregate,
List<Aggregate> chain) throws RepositoryException {
if (parentAggregate == null) {
return null;
}
List<? extends Aggregate> leaves = parentAggregate.getLeaves();
if (leaves == null) {
return null;
}
for (Aggregate leaf : leaves) {
if (leaf.getPath().equals(resource.getPath())) {
chain.add(leaf);
return chain;
} else if (Text.isDescendant(leaf.getPath(), resource.getPath())) {
chain.add(leaf);
return lookForAggregateInLeaves(resource, leaf, chain);
}
}
return null;
}
}