| /* |
| * 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; |
| } |
| } |