/* * 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.cocoon.jcr.source; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.GregorianCalendar; import javax.jcr.Item; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.apache.cocoon.CascadingIOException; import org.apache.excalibur.source.ModifiableTraversableSource; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.SourceNotFoundException; import org.apache.excalibur.source.SourceValidity; import org.apache.excalibur.source.TraversableSource; /** * A Source for a JCR node. * * @version $Id$ */ public class JCRNodeSource implements Source, TraversableSource, ModifiableTraversableSource { /** The full URI */ protected String computedURI; /** The node path */ protected final String path; /** The factory that created this Source */ protected final JCRSourceFactory factory; /** The session this source is bound to */ protected final Session session; /** The node pointed to by this source (can be null) */ protected Node node; public JCRNodeSource(JCRSourceFactory factory, Session session, String path) throws SourceException { this.factory = factory; this.session = session; this.path = path; try { Item item = session.getItem(path); if (!item.isNode()) { throw new SourceException("Path '" + path + "' is a property (should be a node)"); } else { this.node = (Node) item; } } catch (PathNotFoundException e) { // Not found this.node = null; } catch (RepositoryException e) { throw new SourceException("Cannot lookup repository path " + path, e); } } public JCRNodeSource(JCRSourceFactory factory, Node node) throws SourceException { this.factory = factory; this.node = node; try { this.session = node.getSession(); this.path = node.getPath(); } catch (RepositoryException e) { throw new SourceException("Cannot get node's informations", e); } } public JCRNodeSource(JCRNodeSource parent, Node node) throws SourceException { this.factory = parent.factory; this.session = parent.session; this.node = node; try { this.path = getChildPath(parent.path, node.getName()); } catch (RepositoryException e) { throw new SourceException("Cannot get name of child of " + parent.getURI(), e); } } private String getChildPath(String path, String name) { StringBuffer pathBuf = new StringBuffer(path); // Append '/' only if the parent isn't the root (it's path is "/" in // that case) if (pathBuf.length() > 1) pathBuf.append('/'); pathBuf.append(name); return pathBuf.toString(); } /** * Returns the JCR <code>Node</code> this source points to, or * <code>null</code> if it denotes a non-existing path. * * @return the JCR node. */ public Node getNode() { return this.node; } /** * Returns the path within the repository this source points to. * * @return the path */ public String getPath() { return this.path; } /** * Returns the JCR <code>Session</code> used by this source. * * @return the JCR session. */ public Session getSession() { return this.session; } /** * Returns the JCR <code>Node</code> used to store the content of this * source. * * @return the JCR content node, or <code>null</code> if no such node * exist, either because the source is a collection or doesn't * currently contain data. */ public Node getContentNode() { if (this.node == null) { return null; } if (isCollection()) { return null; } try { return this.factory.getContentNode(this.node); } catch (RepositoryException e) { return null; } } // ============================================================================================= // Source interface // ============================================================================================= /* * (non-Javadoc) * * @see org.apache.excalibur.source.Source#exists() */ public boolean exists() { return this.node != null; } /* * (non-Javadoc) * * @see org.apache.excalibur.source.Source#getInputStream() */ public InputStream getInputStream() throws IOException, SourceNotFoundException { if (this.node == null) { throw new SourceNotFoundException("Path '" + this.getURI() + "' does not exist"); } if (this.isCollection()) { throw new SourceException("Path '" + this.getURI() + "' is a collection"); } try { Property contentProp = this.factory.getContentProperty(this.node); return contentProp.getStream(); } catch (Exception e) { throw new SourceException("Error opening stream for '" + this.getURI() + "'", e); } } /* * (non-Javadoc) * * @see org.apache.excalibur.source.Source#getURI() */ public String getURI() { if (this.computedURI == null) { this.computedURI = this.factory.getScheme() + ":/" + this.path; } return this.computedURI; } /* * (non-Javadoc) * * @see org.apache.excalibur.source.Source#getScheme() */ public String getScheme() { return this.factory.getScheme(); } /* * (non-Javadoc) * * @see org.apache.excalibur.source.Source#getValidity() */ public SourceValidity getValidity() { if (!exists()) { return null; } try { Property prop = this.factory.getValidityProperty(this.node); return prop == null ? null : new JCRNodeSourceValidity(prop.getValue()); } catch (RepositoryException re) { return null; } } /* * (non-Javadoc) * * @see org.apache.excalibur.source.Source#refresh() */ public void refresh() { // nothing to do here } /* * (non-Javadoc) * * @see org.apache.excalibur.source.Source#getMimeType() */ public String getMimeType() { if (!exists()) { return null; } try { Property prop = this.factory.getMimeTypeProperty(this.node); if (prop == null) { return null; } else { String value = prop.getString(); return value.length() == 0 ? null : value; } } catch (RepositoryException re) { return null; } } /* * (non-Javadoc) * * @see org.apache.excalibur.source.Source#getContentLength() */ public long getContentLength() { if (!exists()) { return -1; } try { Property prop = this.factory.getContentProperty(this.node); return prop == null ? -1 : prop.getLength(); } catch (RepositoryException re) { return -1; } } /* * (non-Javadoc) * * @see org.apache.excalibur.source.Source#getLastModified() */ public long getLastModified() { if (!exists()) { return 0; } try { Property prop = this.factory.getLastModifiedDateProperty(this.node); return prop == null ? 0 : prop.getDate().getTime().getTime(); } catch (RepositoryException re) { return 0; } } // ============================================================================================= // TraversableSource interface // ============================================================================================= public boolean isCollection() { if (!exists()) return false; try { return this.factory.isCollection(this.node); } catch (RepositoryException e) { return false; } } public Collection getChildren() throws SourceException { if (!isCollection()) { return Collections.EMPTY_LIST; } else { ArrayList children = new ArrayList(); NodeIterator nodes; try { nodes = this.node.getNodes(); } catch (RepositoryException e) { throw new SourceException("Cannot get child nodes for " + getURI(), e); } while (nodes.hasNext()) { children.add(this.factory.createSource(this, nodes.nextNode())); } return children; } } public Source getChild(String name) throws SourceException { if (this.isCollection()) { return this.factory.createSource(this.session, getChildPath(this.path, name)); } else { throw new SourceException("Not a collection: " + getURI()); } } public String getName() { return this.path.substring(this.path.lastIndexOf('/') + 1); } public Source getParent() throws SourceException { if (this.path.length() == 1) { // Root return null; } int lastPos = this.path.lastIndexOf('/'); String parentPath = lastPos == 0 ? "/" : this.path.substring(0, lastPos); return this.factory.createSource(this.session, parentPath); } // ============================================================================================= // ModifiableTraversableSource interface // ============================================================================================= public OutputStream getOutputStream() throws IOException { if (isCollection()) { throw new SourceException("Cannot write to collection " + this.getURI()); } try { Node contentNode; if (!exists()) { JCRNodeSource parent = (JCRNodeSource) getParent(); // Create the path if it doesn't exist parent.makeCollection(); // Create our node this.node = this.factory.createFileNode(parent.node, getName()); contentNode = this.factory.createContentNode(this.node); } else { contentNode = this.factory.getContentNode(this.node); } return new JCRSourceOutputStream(contentNode); } catch (RepositoryException e) { throw new SourceException("Cannot create content node for " + getURI(), e); } } public void delete() throws SourceException { if (exists()) { try { this.node.remove(); this.node = null; this.session.save(); } catch (RepositoryException e) { throw new SourceException("Cannot delete " + getURI(), e); } } } public boolean canCancel(OutputStream os) { if (os instanceof JCRSourceOutputStream) { return ((JCRSourceOutputStream) os).canCancel(); } else { return false; } } public void cancel(OutputStream os) throws IOException { if (canCancel(os)) { ((JCRSourceOutputStream) os).cancel(); } else { throw new IllegalArgumentException("Stream cannot be cancelled"); } } public void makeCollection() throws SourceException { if (exists()) { if (!isCollection()) { throw new SourceException("Cannot make a collection with existing node at " + getURI()); } } else { try { // Ensure parent exists JCRNodeSource parent = (JCRNodeSource) getParent(); if (parent == null) { throw new RuntimeException("Problem: root node does not exist!!"); } parent.makeCollection(); Node parentNode = parent.node; String typeName = this.factory.getFolderNodeType(parentNode); this.node = parentNode.addNode(getName(), typeName); this.session.save(); } catch (RepositoryException e) { throw new SourceException("Cannot make collection " + this.getURI(), e); } } } // ---------------------------------------------------------------------------------- // Private helper class for ModifiableSource implementation // ---------------------------------------------------------------------------------- /** * An outputStream that will save the session upon close, and discard it * upon cancel. */ private class JCRSourceOutputStream extends ByteArrayOutputStream { private boolean isClosed = false; private final Node contentNode; public JCRSourceOutputStream(Node contentNode) { this.contentNode = contentNode; } public void close() throws IOException { if (!isClosed) { super.close(); this.isClosed = true; try { JCRSourceFactory.ContentTypeInfo info = (JCRSourceFactory.ContentTypeInfo) factory.getTypeInfo(contentNode); contentNode.setProperty(info.contentProp, new ByteArrayInputStream(this.toByteArray())); if (info.lastModifiedProp != null) { contentNode.setProperty(info.lastModifiedProp, new GregorianCalendar()); } if (info.mimeTypeProp != null) { // FIXME: define mime type contentNode.setProperty(info.mimeTypeProp, ""); } JCRNodeSource.this.session.save(); } catch (RepositoryException e) { throw new CascadingIOException("Cannot save content to " + getURI(), e); } } } public boolean canCancel() { return !isClosed; } public void cancel() throws IOException { if (isClosed) { throw new IllegalStateException("Cannot cancel : outputstrem is already closed"); } } } }