| /* |
| * 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.jackrabbit.vault.fs.io; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.annotation.Nonnull; |
| import javax.jcr.Node; |
| import javax.jcr.NodeIterator; |
| import javax.jcr.RepositoryException; |
| |
| import org.apache.jackrabbit.vault.fs.api.PathFilterSet; |
| import org.apache.jackrabbit.vault.fs.api.VaultInputSource; |
| import org.apache.jackrabbit.vault.fs.config.ConfigurationException; |
| import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf; |
| import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; |
| import org.apache.jackrabbit.vault.fs.config.MetaInf; |
| import org.apache.jackrabbit.vault.fs.config.VaultSettings; |
| import org.apache.jackrabbit.vault.util.Constants; |
| import org.apache.jackrabbit.vault.util.JcrConstants; |
| import org.apache.jackrabbit.vault.util.Text; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Implements an archive that is based on nt:file/nt:folder nodes in a JCR |
| * repository. |
| */ |
| public class JcrArchive extends AbstractArchive { |
| |
| /** |
| * default logger |
| */ |
| private static final Logger log = LoggerFactory.getLogger(JcrArchive.class); |
| |
| private Node archiveRoot; |
| |
| private final String rootPath; |
| |
| private DefaultMetaInf inf; |
| |
| private Entry jcrRoot; |
| |
| // the 'chrooted' root. currently not used. |
| private String chRoot; |
| |
| /** |
| * Creates a new JCR archive rooted at the given node. |
| * @param archiveRoot root node for this archive |
| * @param rootPath root path |
| */ |
| public JcrArchive(@Nonnull Node archiveRoot, @Nonnull String rootPath) { |
| this.archiveRoot = archiveRoot; |
| this.rootPath = rootPath; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void open(boolean strict) throws IOException { |
| if (jcrRoot != null) { |
| return; |
| } |
| try { |
| if (archiveRoot.hasNode(Constants.ROOT_DIR)) { |
| jcrRoot = new JcrEntry(archiveRoot.getNode(Constants.ROOT_DIR), Constants.ROOT_DIR, true); |
| } else { |
| jcrRoot = new JcrEntry(archiveRoot, archiveRoot.getName(), true); |
| } |
| if (archiveRoot.hasNode(Constants.META_DIR)) { |
| inf = loadMetaInf(new JcrEntry(archiveRoot.getNode(Constants.META_DIR), Constants.META_DIR, true)); |
| } else { |
| inf = new DefaultMetaInf(); |
| inf.setSettings(VaultSettings.createDefault()); |
| DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); |
| PathFilterSet filterSet = new PathFilterSet(rootPath); |
| filter.add(filterSet); |
| inf.setFilter(filter); |
| |
| // if archive is ch-rooted, create intermediate entries |
| if (chRoot != null && chRoot.length() > 0) { |
| String[] roots = Text.explode(rootPath, '/'); |
| if (roots.length > 0) { |
| VirtualEntry newRoot = new VirtualEntry(jcrRoot.getName()); |
| VirtualEntry entry = newRoot; |
| for (String name: roots) { |
| VirtualEntry newEntry = new VirtualEntry(name); |
| entry.children.put(name, newEntry); |
| entry = newEntry; |
| } |
| for (Entry e: jcrRoot.getChildren()) { |
| entry.children.put(e.getName(), e); |
| } |
| jcrRoot = newRoot; |
| } |
| } |
| } |
| } catch (RepositoryException | ConfigurationException e) { |
| throw new IOException("Error while opening JCR archive.", e); |
| } |
| } |
| |
| private DefaultMetaInf loadMetaInf(Entry dir) |
| throws IOException, ConfigurationException { |
| DefaultMetaInf inf = new DefaultMetaInf(); |
| // filter |
| for (Entry entry: dir.getChildren()) { |
| VaultInputSource src = getInputSource(entry); |
| try (InputStream input = src.getByteStream()) { |
| inf.load(input, src.getSystemId()); |
| } |
| } |
| if (inf.getFilter() == null) { |
| log.debug("Archive {} does not contain filter definition.", this); |
| } |
| if (inf.getConfig() == null) { |
| log.debug("Archive {} does not contain vault config.", this); |
| } |
| if (inf.getSettings() == null) { |
| log.debug("Archive {} does not contain vault settings. using default.", this); |
| VaultSettings settings = new VaultSettings(); |
| settings.getIgnoredNames().add(".svn"); |
| inf.setSettings(settings); |
| } |
| if (inf.getProperties() == null) { |
| log.debug("Archive {} does not contain properties.", this); |
| } |
| if (inf.getNodeTypes().isEmpty()) { |
| log.debug("Archive {} does not contain nodetypes.", this); |
| } |
| return inf; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void close() { |
| archiveRoot = null; |
| jcrRoot = null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Entry getJcrRoot() { |
| return jcrRoot; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Entry getRoot() throws IOException { |
| return new JcrEntry(archiveRoot, "", true); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public MetaInf getMetaInf() { |
| return inf; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public InputStream openInputStream(Entry entry) throws IOException { |
| if (entry == null || entry.isDirectory()) { |
| return null; |
| } |
| try { |
| Node content = ((JcrEntry) entry).node.getNode(JcrConstants.JCR_CONTENT); |
| return content.getProperty(JcrConstants.JCR_DATA).getBinary().getStream(); |
| } catch (RepositoryException e) { |
| throw new IOException("Unable to open input source.", e); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public VaultInputSource getInputSource(final Entry entry) throws IOException { |
| if (entry == null || entry.isDirectory()) { |
| return null; |
| } |
| try { |
| final Node content = ((JcrEntry) entry).node.getNode(JcrConstants.JCR_CONTENT); |
| final String systemId = ((JcrEntry) entry).node.getPath(); |
| return new VaultInputSource() { |
| |
| { |
| setSystemId(systemId); |
| } |
| |
| public InputStream getByteStream() { |
| try { |
| // todo: handle releasing of binary ? |
| return content.getProperty(JcrConstants.JCR_DATA).getBinary().getStream(); |
| } catch (RepositoryException e) { |
| log.error("Error while opening input stream of " + content, e); |
| return null; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public long getContentLength() { |
| try { |
| return content.getProperty(JcrConstants.JCR_DATA).getLength(); |
| } catch (RepositoryException e) { |
| log.error("Error while retrieving length of " + content, e); |
| return -1; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public long getLastModified() { |
| try { |
| return content.getProperty(JcrConstants.JCR_LASTMODIFIED).getDate().getTimeInMillis(); |
| } catch (RepositoryException e) { |
| log.error("Error while retrieving last modified of " + content, e); |
| return 0; |
| } |
| } |
| |
| }; |
| |
| } catch (RepositoryException e) { |
| throw new IOException("Unable to open input source.", e); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| try { |
| return archiveRoot.getPath(); |
| } catch (RepositoryException e) { |
| return archiveRoot.toString(); |
| } |
| } |
| |
| /** |
| * Implements a virtual entry for the intermediate directories |
| */ |
| private static class VirtualEntry implements Entry { |
| |
| private final String name; |
| |
| private Map<String, Entry> children = new LinkedHashMap<String, Entry>(); |
| |
| private VirtualEntry(String name) { |
| this.name = name; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isDirectory() { |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Collection<? extends Entry> getChildren() { |
| return children.values(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Entry getChild(String name) { |
| return children.get(name); |
| } |
| } |
| |
| /** |
| * Implements an archive entry that is based on a JCR node |
| */ |
| private static class JcrEntry implements Entry { |
| |
| private final Node node; |
| |
| private final boolean isDir; |
| |
| private final String name; |
| |
| private JcrEntry(Node node, String name, boolean isDir) { |
| this.node = node; |
| this.isDir = isDir; |
| this.name = name; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isDirectory() { |
| return isDir; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Collection<Entry> getChildren() { |
| if (isDir) { |
| try { |
| NodeIterator iter = node.getNodes(); |
| long size = iter.getSize(); |
| if (size < 0) { |
| size = 0; |
| } |
| List<Entry> ret = new ArrayList<Entry>((int) size); |
| while (iter.hasNext()) { |
| Node child = iter.nextNode(); |
| String name = child.getName(); |
| if (".svn".equals(name)) { |
| // skip already |
| continue; |
| } |
| boolean isDir; |
| if (child.isNodeType("nt:folder")) { |
| isDir = true; |
| } else if (child.isNodeType("nt:file")) { |
| isDir = false; |
| } else { |
| log.debug("Skipping node {} with unknown type {}.", child.getPath(), child.getPrimaryNodeType().getName()); |
| continue; |
| } |
| ret.add(new JcrEntry(child, name, isDir)); |
| } |
| return ret; |
| } catch (RepositoryException e) { |
| log.error("Error while listing child nodes of {}", node, e); |
| throw new IllegalStateException("Error while listing child nodes of " + node, e); |
| } |
| } |
| return Collections.emptyList(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Entry getChild(String name) { |
| try { |
| if (isDir && node.hasNode(name) && !".svn".equals(name)) { |
| Node child = node.getNode(name); |
| boolean isDir; |
| if (child.isNodeType("nt:folder")) { |
| isDir = true; |
| } else if (child.isNodeType("nt:file")) { |
| isDir = false; |
| } else { |
| log.debug("Skipping node {} with unknown type {}.", child.getPath(), child.getPrimaryNodeType().getName()); |
| return null; |
| } |
| return new JcrEntry(child, name, isDir); |
| } |
| return null; |
| } catch (RepositoryException e) { |
| log.error("Error while retrieving child node of {}", node, e); |
| throw new IllegalStateException("Error while retrieving child node of " + node, e); |
| } |
| } |
| } |
| |
| } |