/* * 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.components.source.impl; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.List; import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.context.Context; import org.apache.avalon.framework.context.ContextException; import org.apache.avalon.framework.context.Contextualizable; import org.apache.avalon.framework.logger.AbstractLogEnabled; import org.apache.avalon.framework.logger.LogEnabled; import org.apache.avalon.framework.logger.Logger; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; import org.apache.cocoon.CascadingIOException; import org.apache.cocoon.Constants; import org.apache.cocoon.components.source.InspectableSource; import org.apache.cocoon.components.source.LockableSource; import org.apache.cocoon.components.source.VersionableSource; import org.apache.cocoon.components.source.helpers.SourceLock; import org.apache.cocoon.components.source.helpers.SourceProperty; import org.apache.excalibur.source.ModifiableTraversableSource; import org.apache.excalibur.source.MoveableSource; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.SourceUtil; import org.apache.excalibur.source.SourceValidity; import org.apache.excalibur.source.impl.validity.TimeStampValidity; import org.apache.excalibur.xml.dom.DOMParser; import org.apache.slide.authenticate.CredentialsToken; import org.apache.slide.common.NamespaceAccessToken; import org.apache.slide.common.SlideException; import org.apache.slide.common.SlideToken; import org.apache.slide.common.SlideTokenImpl; import org.apache.slide.content.Content; import org.apache.slide.content.NodeProperty; import org.apache.slide.content.NodeRevisionContent; import org.apache.slide.content.NodeRevisionDescriptor; import org.apache.slide.content.NodeRevisionDescriptors; import org.apache.slide.content.NodeRevisionNumber; import org.apache.slide.content.RevisionDescriptorNotFoundException; import org.apache.slide.lock.Lock; import org.apache.slide.lock.NodeLock; import org.apache.slide.macro.Macro; import org.apache.slide.security.AccessDeniedException; import org.apache.slide.structure.ObjectNode; import org.apache.slide.structure.ObjectNotFoundException; import org.apache.slide.structure.Structure; import org.apache.slide.structure.SubjectNode; import org.w3c.dom.Document; import org.xml.sax.InputSource; /** * A sources from jakarta slide repositories. * * @version CVS $Id$ */ public class SlideSource extends AbstractLogEnabled implements Contextualizable, Serviceable, Initializable, Source, ModifiableTraversableSource, MoveableSource, LockableSource, InspectableSource, VersionableSource { /* framework objects */ private Context m_context; private ServiceManager m_manager; /* Slide access */ private NamespaceAccessToken m_nat; private SlideToken m_slideToken; /* Slide helpers */ private Structure m_structure; private Content m_content; private Lock m_lock; private Macro m_macro; /* Source specifics */ private String m_scheme; private String m_path; private String m_scope; private String m_uri; private ObjectNode m_node; private NodeRevisionNumber m_version; private NodeRevisionDescriptors m_descriptors; private NodeRevisionDescriptor m_descriptor; private String m_principal; private SourceValidity m_validity; private SlideSourceOutputStream m_outputStream; /** * Create a slide source. * * @param nat Namespace access token * @param scheme Scheme of the source * @param path Path of the source. */ public SlideSource(NamespaceAccessToken nat, String scheme, String scope, String path, String principal, String version) { m_nat = nat; m_scheme = scheme; m_scope = scope; m_path = path; if (path.equals("/")) { m_uri = scope; } else if (scope.equals("/")){ m_uri = path; } else { m_uri = scope + path; } m_principal = principal; if (version != null) { m_version = new NodeRevisionNumber(version); } } /** * Pass the Context to the component. * This method is called after the LogEnabled.enableLogging() (if present) * method and before any other method. * * @param context The context. */ public void contextualize(Context context) { this.m_context = context; } /** * Pass the ServiceManager to the composer. The Serviceable implementation * should use the specified ServiceManager to acquire the services it needs for execution * * @param manager The ServiceManager which this Serviceable uses */ public void service(ServiceManager manager) { m_manager = manager; } public void initialize() throws SourceException { CredentialsToken credentials = new CredentialsToken(m_principal); m_slideToken = new SlideTokenImpl(credentials); m_structure = m_nat.getStructureHelper(); m_content = m_nat.getContentHelper(); m_lock = m_nat.getLockHelper(); m_macro = m_nat.getMacroHelper(); try { if (m_node == null) { m_node = m_structure.retrieve(m_slideToken,m_uri); } m_descriptors = m_content.retrieve(m_slideToken,m_uri); if (m_version != null) { // get a specific version m_descriptor = m_content.retrieve(m_slideToken,m_descriptors,m_version); } else { // get the latest one m_descriptor = m_content.retrieve(m_slideToken,m_descriptors); m_version = m_descriptor.getRevisionNumber(); } } catch (ObjectNotFoundException e) { if (getLogger().isDebugEnabled()) { getLogger().debug("Not found.",e); } // assert m_node == null; } catch (RevisionDescriptorNotFoundException e) { if (getLogger().isDebugEnabled()) { getLogger().debug("Could not retrieve descriptor.",e); } // assert m_descriptor == null; } catch (AccessDeniedException e) { throw new SourceException("Access denied.",e); } catch (SlideException e) { throw new SourceException("Failure during source initialization.",e); } } /** * Return an <code>InputStream</code> object to read from the source. * This is the data at the point of invocation of this method, * so if this is Modifiable, you might get different content * from two different invocations. * * @return Input stream for the source. * * @throws IOException If an IO excepetion occurs. * @throws SourceException If an exception occurs. */ public InputStream getInputStream() throws IOException, SourceException { try { return m_content.retrieve(m_slideToken,m_descriptors,m_descriptor).streamContent(); } catch (SlideException se) { throw new SourceException("Could not get source", se); } } /** * Return the unique identifer for this source * * @return System identifier for the source. */ public String getURI() { return m_scheme + "://" + m_principal + "@" + m_nat.getName() + m_path; } /** * @see org.apache.excalibur.source.Source#getScheme() * * @return Scheme of the source. */ public String getScheme() { return m_scheme; } /** * Get the Validity object. This can either wrap the last modification * date or the expires information or... * If it is currently not possible to calculate such an information * <code>null</code> is returned. * * @return Validity for the source. */ public SourceValidity getValidity() { if (m_validity == null && m_descriptor != null) { final long lastModified = getLastModified(); if (lastModified > 0) { m_validity = new TimeStampValidity(lastModified); } } return m_validity; } /** * Refresh the content of this object after the underlying data * content has changed. */ public void refresh() { m_validity = null; } /** * The mime-type of the content described by this object. * If the source is not able to determine the mime-type by itself * this can be null. * * @return Mime type of the source. */ public String getMimeType() { if (m_descriptor != null) { return m_descriptor.getContentType(); } return null; } /** * Does this source actually exist ? * * @return true if the resource exists. */ public boolean exists() { return m_node != null; } /** * Return the content length of the content or -1 if the length is * unknown. * * @return Content length of the source. */ public long getContentLength() { if (m_descriptor != null) { return m_descriptor.getContentLength(); } return -1; } /** * Get the last modification date of the source or 0 if it * is not possible to determine the date. * * @return Last modified date of the source. */ public long getLastModified() { if (m_descriptor != null) { return m_descriptor.getLastModifiedAsDate().getTime(); } return 0; } // ---------------------------------------------------- ModifiableTraversableSource /** * Get an <code>OutputStream</code> where raw bytes can be written to. * The signification of these bytes is implementation-dependent and * is not restricted to a serialized XML document. * * @return a stream to write to * * @throws IOException * @throws SourceException */ public OutputStream getOutputStream() throws IOException, SourceException { if (m_outputStream == null) { m_outputStream = new SlideSourceOutputStream(); m_outputStream.enableLogging(getLogger()); } return m_outputStream; } /** * Can the data sent to an <code>OutputStream</code> returned by * {@link #getOutputStream()} be cancelled ? * * @param stream The ouput stream, which should be cancelled. * @return true if the stream can be cancelled */ public boolean canCancel(OutputStream stream) { return m_outputStream.canCancel(); } /** * Cancel the data sent to an <code>OutputStream</code> returned by * {@link #getOutputStream()}. * <p> * After cancel, the stream should no more be used. * * @param stream The ouput stream, which should be cancelled. * * @throws SourceException If the ouput stream can't be cancelled. */ public void cancel(OutputStream stream) throws SourceException { if (m_outputStream == stream) { try { m_outputStream.cancel(); } catch (Exception e) { throw new SourceException("Could not cancel output stream",e); } } } /** * Delete the source. */ public void delete() { try { m_nat.begin(); m_macro.delete(m_slideToken,m_uri); m_nat.commit(); } catch (Exception se) { getLogger().error("Could not delete source.",se); try { m_nat.rollback(); } catch (Exception rbe) { getLogger().error("Rollback failed for moving source",rbe); } } } public void makeCollection() throws SourceException { SubjectNode collection = new SubjectNode(); NodeRevisionDescriptor descriptor = new NodeRevisionDescriptor(0); descriptor.setResourceType("<collection/>"); descriptor.setCreationDate(new Date()); descriptor.setLastModified(new Date()); descriptor.setContentLength(0); descriptor.setSource(""); descriptor.setOwner(m_slideToken.getCredentialsToken().getPublicCredentials()); try { m_nat.begin(); m_structure.create(m_slideToken,collection,m_uri); m_content.create(m_slideToken,m_uri,descriptor,null); m_nat.commit(); } catch (Exception se) { try { m_nat.rollback(); } catch (Exception rbe) { getLogger().error("Rollback failed for creating collection", rbe); } throw new SourceException("Could not create collection.", se); } } public Source getChild(String name) throws SourceException { return getChildByPath(m_path+"/"+name); } private Source getChildByPath(String path) throws SourceException { SlideSource child = new SlideSource(m_nat,m_scheme,m_scope,path,m_principal,null); child.enableLogging(getLogger()); child.contextualize(m_context); child.service(m_manager); child.initialize(); return child; } public Collection getChildren() throws SourceException { if (m_node == null || !m_node.hasChildren()) { return Collections.EMPTY_LIST; } List result = new ArrayList(); final Enumeration children = m_node.enumerateChildren(); while (children.hasMoreElements()) { String child = (String) children.nextElement(); child = child.substring(m_scope.length()); result.add(getChildByPath(child)); } return result; } public String getName() { int index = m_path.lastIndexOf('/'); if (index != -1) { return m_path.substring(index+1); } return m_path; } public Source getParent() throws SourceException { if (m_path.length() == 1) { // assert m_path.equals("/") return null; } int index = m_path.lastIndexOf('/'); if (index == -1) { return null; } String parentPath; if (index == 0) { parentPath = "/"; } else if (index == m_path.length()-1) { // assert m_path.endsWith("/") parentPath = m_path.substring(0,m_path.substring(0, m_path.length()-1).lastIndexOf('/')); } else { parentPath = m_path.substring(0,index); } SlideSource parent = new SlideSource(m_nat,m_scheme,m_scope,parentPath,m_principal,null); parent.enableLogging(getLogger()); parent.contextualize(m_context); parent.service(m_manager); parent.initialize(); return parent; } public boolean isCollection() { if (m_node == null) { return false; } if (m_descriptor == null) { // FIXME: is this correct? return true; } NodeProperty property = m_descriptor.getProperty("resourcetype"); if (property != null && ((String) property.getValue()).startsWith("<collection/>")) { return true; } return false; } /** * A helper for the getOutputStream() method */ class SlideSourceOutputStream extends ByteArrayOutputStream implements LogEnabled { private boolean isClosed = false; private Logger logger = null; /** * Provide component with a logger. * * @param logger the logger */ public void enableLogging(Logger logger) { this.logger = logger; } /** * * * @throws IOException */ public void close() throws IOException { super.close(); byte[] bytes = new byte[0]; // must be initialized try { NodeRevisionContent content = new NodeRevisionContent(); bytes = toByteArray(); content.setContent(bytes); if (m_descriptor == null) { m_descriptor = new NodeRevisionDescriptor(0); m_descriptor.setName(getName()); } m_descriptor.setContentLength(bytes.length); m_descriptor.setLastModified(new Date()); m_nat.begin(); if (m_version == null) { m_content.create(m_slideToken,m_uri,m_descriptor,null); } m_content.store(m_slideToken,m_uri,m_descriptor,content); try { m_nat.commit(); } catch (Exception cme) { throw new CascadingIOException("Could not commit the transaction",cme); } } catch (ObjectNotFoundException e) { // Todo : Check to see if parent exists SubjectNode subject = new SubjectNode(); try { // Creating an object m_structure.create(m_slideToken,subject,m_uri); } catch (SlideException se) { throw new CascadingIOException(se); } NodeRevisionDescriptor descriptor = new NodeRevisionDescriptor(bytes.length); descriptor.setResourceType(""); descriptor.setSource(""); descriptor.setContentLanguage("en"); descriptor.setContentLength(bytes.length); String contentType = null; try { contentType = ((org.apache.cocoon.environment.Context) m_context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT)).getMimeType(m_path); } catch (ContextException ce) { this.logger.warn("Could not get context to determine the mime type."); } if (contentType == null) { contentType = "application/octet-stream"; } descriptor.setContentType(contentType); descriptor.setLastModified(new Date()); descriptor.setOwner(m_slideToken.getCredentialsToken().getPublicCredentials()); NodeRevisionContent content = new NodeRevisionContent(); content.setContent(bytes); try { m_content.create(m_slideToken,m_uri,descriptor,content); try { m_nat.commit(); } catch (Exception cme) { throw new CascadingIOException("Could not commit the transaction",cme); } } catch (SlideException se) { try { m_nat.rollback(); } catch (Exception rbe) { this.logger.warn("Could not rollback the transaction.",rbe); } throw new CascadingIOException("Could not create source",se); } } catch (Exception e) { if (e instanceof IOException) { throw (IOException) e; } throw new CascadingIOException("Could not create source", e); } finally { this.isClosed = true; } } /** * Can the data sent to an <code>OutputStream</code> returned by * {@link SlideSource#getOutputStream()} be cancelled ? * * @return true if the stream can be cancelled */ boolean canCancel() { return !this.isClosed; } /** * Cancel the data sent to an <code>OutputStream</code> returned by * {@link SlideSource#getOutputStream()}. * <p> * After cancel, the stream should no more be used. * */ void cancel() throws Exception { if (this.isClosed) { throw new IllegalStateException("Cannot cancel : outputstrem is already closed"); } this.isClosed = true; super.close(); } } // ---------------------------------------------------- MoveableSource /** * Move the current source to a specified destination. * * @param source * * @throws SourceException If an exception occurs during the move. */ public void moveTo(Source source) throws SourceException { if (source instanceof SlideSource) { try { m_nat.begin(); String destination = m_scope+((SlideSource) source).m_path; m_macro.move(m_slideToken,m_uri,destination); m_nat.commit(); } catch (Exception se) { try { m_nat.rollback(); } catch (Exception rbe) { getLogger().error("Rollback failed for moving source", rbe); } throw new SourceException("Could not move source.", se); } } else { SourceUtil.move(this,source); } } /** * Copy the current source to a specified destination. * * @param source * * @throws SourceException If an exception occurs during the copy. */ public void copyTo(Source source) throws SourceException { if (source instanceof SlideSource) { try { m_nat.begin(); String destination = m_scope+((SlideSource) source).m_path; m_macro.copy(m_slideToken,m_uri,destination); m_nat.commit(); } catch (Exception se) { try { m_nat.rollback(); } catch (Exception rbe) { getLogger().error("Rollback failed for moving source",rbe); } throw new SourceException("Could not move source.",se); } } else { SourceUtil.copy(this,source); } } // ---------------------------------------------------- InspectableSource /** * Returns a property from a source. * * @param namespace Namespace of the property * @param name Name of the property * * @return Property of the source. * * @throws SourceException If an exception occurs. */ public SourceProperty getSourceProperty(String namespace, String name) throws SourceException { if (m_descriptor == null) { return null; } final String quote = "\""; NodeProperty property = m_descriptor.getProperty(name, namespace); if (property == null) { return null; } String pre = "<"+name+" xmlns="+quote+namespace+quote+" >"; String post = "</"+name+" >"; StringReader reader = new StringReader(pre+property.getValue().toString()+post); InputSource src = new InputSource(reader); DOMParser parser = null; Document doc = null; try { parser = (DOMParser) this.m_manager.lookup(DOMParser.ROLE); doc = parser.parseDocument(src); } catch (Exception e) { throw new SourceException("Could not parse property", e); } finally { this.m_manager.release(parser); } return new SourceProperty(doc.getDocumentElement()); } /** * Sets a property for a source. * * @param property Property of the source * * @throws SourceException If an exception occurs during this operation */ public void setSourceProperty(SourceProperty property) throws SourceException { try { m_descriptor.setProperty(property.getName(), property.getNamespace(), property.getValueAsString()); m_descriptor.setLastModified(new Date()); m_nat.begin(); m_content.store(m_slideToken,m_uri,m_descriptor,null); m_nat.commit(); } catch (Exception se) { try { m_nat.rollback(); } catch (Exception rbe) { getLogger().error("Rollback failed for setting a source property", rbe); } throw new SourceException("Could not set source property", se); } } /** * Returns a enumeration of the properties * * @return Enumeration of SourceProperty * * @throws SourceException If an exception occurs. */ public SourceProperty[] getSourceProperties() throws SourceException { if (m_descriptor == null) { return new SourceProperty[0]; } List properties = new ArrayList(); DOMParser parser = null; String xml = ""; try { parser = (DOMParser) m_manager.lookup(DOMParser.ROLE); final String quote = "\""; Enumeration e = m_descriptor.enumerateProperties(); while (e.hasMoreElements()) { NodeProperty property = (NodeProperty) e.nextElement(); String name = property.getName(); String namespace = property.getNamespace(); String pre = "<"+name+" xmlns="+quote+namespace+quote+" >"; String post = "</"+name+" >"; xml = pre+property.getValue().toString()+post; StringReader reader = new StringReader(xml); Document doc = parser.parseDocument(new InputSource(reader)); properties.add(new SourceProperty(doc.getDocumentElement())); } } catch (Exception e) { throw new SourceException("Could not parse property "+xml, e); } finally { m_manager.release(parser); } return (SourceProperty[]) properties.toArray(new SourceProperty[properties.size()]); } /** * Remove a specified source property. * * @param namespace Namespace of the property. * @param name Name of the property. * * @throws SourceException If an exception occurs. */ public void removeSourceProperty(String namespace, String name) throws SourceException { try { if (m_descriptor != null && !namespace.equals("DAV:")) { m_descriptor.removeProperty(name, namespace); m_descriptor.setLastModified(new Date()); m_nat.begin(); m_content.store(m_slideToken,m_uri,m_descriptor,null); m_nat.commit(); } } catch (Exception se) { try { m_nat.rollback(); } catch (Exception rbe) { getLogger().error("Rollback failed for removing a source property", rbe); } throw new SourceException("Could not remove property", se); } } // ---------------------------------------------------- LockableSource /** * Add a lock to this source * * @param sourcelock Lock, which should be added * * @throws SourceException If an exception occurs during this operation */ public void addSourceLocks(SourceLock sourcelock) throws SourceException { throw new SourceException("Operation not yet supported"); } /** * Returns a enumeration of the existing locks * * @return Enumeration of SourceLock * * @throws SourceException If an exception occurs. */ public SourceLock[] getSourceLocks() throws SourceException { try { List result = new ArrayList(); NodeLock lock; Enumeration locks = m_lock.enumerateLocks(m_slideToken,m_uri, false); while (locks.hasMoreElements()) { lock = (NodeLock) locks.nextElement(); result.add(new SourceLock(lock.getSubjectUri(), lock.getTypeUri(), lock.getExpirationDate(), lock.isInheritable(), lock.isExclusive())); } return (SourceLock[]) result.toArray(new SourceLock[result.size()]); } catch (SlideException se) { throw new SourceException("Could not retrieve locks", se); } } // ---------------------------------------------------- VersionableSource /** * If this source versioned * * @return True if the current source is versioned. * * @throws SourceException If an exception occurs. */ public boolean isVersioned() throws SourceException { if (m_descriptors != null) { return m_descriptors.hasRevisions(); } return false; } /** * Get the current revision of the source * * @return The current revision of the source * */ public String getSourceRevision() { if (m_version != null) { return m_version.toString(); } return null; } /** * Not implemented. * * @param revision The revision, which should be used. * * @throws SourceException If an exception occurs. */ public void setSourceRevision(String revision) throws SourceException { // [UH] this method is wrong. different versions should be obtained // by creating a new source throw new SourceException("method not implemented"); } /** * Get the current branch of the revision from the source * * @return The branch of the revision * * @throws SourceException If an exception occurs. */ public String getSourceRevisionBranch() throws SourceException { if (m_descriptor != null) { return m_descriptor.getBranchName(); } return null; } /** * Not implemented. * * @param branch The branch, which should be used. * * @throws SourceException If an exception occurs. */ public void setSourceRevisionBranch(String branch) throws SourceException { // [UH] this method is wrong. different versions should be obtained // by creating a new source throw new SourceException("method not implemented"); } /** * Get the latest revision * * @return Last revision of the source. * * @throws SourceException If an exception occurs. */ public String getLatestSourceRevision() throws SourceException { if (m_descriptors != null) { return m_descriptors.getLatestRevision().toString(); } return null; } }