blob: f7c25a856bbfc3a44dc82b58c7a8556977e05836 [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.lenya.cms.publication;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.avalon.framework.container.ContainerUtil;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.ServiceSelector;
import org.apache.lenya.cms.cocoon.source.SourceUtil;
import org.apache.lenya.cms.metadata.MetaData;
import org.apache.lenya.cms.metadata.MetaDataException;
import org.apache.lenya.cms.publication.util.DocumentVisitor;
import org.apache.lenya.cms.repository.ContentHolder;
import org.apache.lenya.cms.repository.Node;
import org.apache.lenya.cms.repository.NodeFactory;
import org.apache.lenya.cms.repository.RepositoryException;
import org.apache.lenya.cms.repository.Session;
import org.apache.lenya.cms.site.Link;
import org.apache.lenya.cms.site.SiteException;
import org.apache.lenya.cms.site.SiteStructure;
import org.apache.lenya.util.Assert;
/**
* A typical CMS document.
* @version $Id$
*/
public class DocumentImpl extends AbstractLogEnabled implements Document {
private DocumentIdentifier identifier;
private DocumentFactory factory;
protected ServiceManager manager;
private int revision = -1;
/**
* The meta data namespace.
*/
public static final String METADATA_NAMESPACE = "http://apache.org/lenya/metadata/document/1.0";
/**
* The name of the resource type attribute. A resource has a resource type; this information can
* be used e.g. for different rendering of different types.
*/
protected static final String METADATA_RESOURCE_TYPE = "resourceType";
/**
* The name of the mime type attribute.
*/
protected static final String METADATA_MIME_TYPE = "mimeType";
/**
* The name of the content type attribute. Any content managed by Lenya has a type; this
* information can be used e.g. to provide an appropriate management interface.
*/
protected static final String METADATA_CONTENT_TYPE = "contentType";
/**
* The number of seconds from the request that a document can be cached before it expires
*/
protected static final String METADATA_EXPIRES = "expires";
/**
* The extension to use for the document source.
*/
protected static final String METADATA_EXTENSION = "extension";
/**
* Creates a new instance of DefaultDocument.
* @param manager The service manager.
* @param map The identity map the document belongs to.
* @param identifier The identifier.
* @param revision The revision number or -1 if the latest revision should be used.
* @param _logger a logger
*/
protected DocumentImpl(ServiceManager manager, DocumentFactory map,
DocumentIdentifier identifier, int revision, Logger _logger) {
ContainerUtil.enableLogging(this, _logger);
if (getLogger().isDebugEnabled()) {
getLogger().debug(
"DefaultDocument() creating new instance with id [" + identifier.getUUID()
+ "], language [" + identifier.getLanguage() + "]");
}
if (identifier.getUUID() == null) {
throw new IllegalArgumentException("The UUID must not be null!");
}
this.manager = manager;
this.identifier = identifier;
this.factory = map;
this.revision = revision;
if (getLogger().isDebugEnabled()) {
getLogger().debug(
"DefaultDocument() done building instance with _id [" + identifier.getUUID()
+ "], _language [" + identifier.getLanguage() + "]");
}
}
/**
* @see org.apache.lenya.cms.publication.Document#getExpires()
*/
public Date getExpires() throws DocumentException {
Date expires = null;
long secs = 0;
MetaData metaData = null;
String expiresMeta = null;
try {
metaData = this.getMetaData(METADATA_NAMESPACE);
expiresMeta = metaData.getFirstValue("expires");
} catch (MetaDataException e) {
throw new DocumentException(e);
}
if (expiresMeta != null) {
secs = Long.parseLong(expiresMeta);
} else {
secs = -1;
}
if (secs != -1) {
Date date = new Date();
date.setTime(date.getTime() + secs * 1000l);
expires = date;
} else {
expires = this.getResourceType().getExpires();
}
return expires;
}
/**
* @see org.apache.lenya.cms.publication.Document#getName()
*/
public String getName() {
try {
return getLink().getNode().getName();
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
private Publication publication;
/**
* @see org.apache.lenya.cms.publication.Document#getPublication()
*/
public Publication getPublication() {
if (this.publication == null) {
try {
this.publication = getFactory().getPublication(getIdentifier().getPublicationId());
} catch (PublicationException e) {
throw new RuntimeException(e);
}
}
return this.publication;
}
/**
* @see org.apache.lenya.cms.publication.Document#getLastModified()
*/
public long getLastModified() throws DocumentException {
try {
return getRepositoryNode().getLastModified();
} catch (RepositoryException e) {
throw new DocumentException(e);
}
}
public File getFile() {
return getPublication().getPathMapper().getFile(getPublication(), getArea(), getUUID(),
getLanguage());
}
/**
* @see org.apache.lenya.cms.publication.Document#getLanguage()
*/
public String getLanguage() {
return this.identifier.getLanguage();
}
/**
* @see org.apache.lenya.cms.publication.Document#getLanguages()
*/
public String[] getLanguages() throws DocumentException {
List documentLanguages = new ArrayList();
String[] allLanguages = getPublication().getLanguages();
if (getLogger().isDebugEnabled()) {
getLogger().debug("Number of languages of this publication: " + allLanguages.length);
}
for (int i = 0; i < allLanguages.length; i++) {
if (existsTranslation(allLanguages[i])) {
documentLanguages.add(allLanguages[i]);
}
}
return (String[]) documentLanguages.toArray(new String[documentLanguages.size()]);
}
/**
* @see org.apache.lenya.cms.publication.Document#getArea()
*/
public String getArea() {
return this.identifier.getArea();
}
private String extension = null;
private String defaultExtension = "html";
/**
* @see org.apache.lenya.cms.publication.Document#getExtension()
*/
public String getExtension() {
if (extension == null) {
String sourceExtension = getSourceExtension();
if (sourceExtension.equals("xml") || sourceExtension.equals("")) {
getLogger().info("Default extension will be used: " + defaultExtension);
return defaultExtension;
} else {
return sourceExtension;
}
}
return this.extension;
}
/**
* @see org.apache.lenya.cms.publication.Document#getUUID()
*/
public String getUUID() {
return getIdentifier().getUUID();
}
private String defaultSourceExtension = "xml";
/**
* @see org.apache.lenya.cms.publication.Document#getSourceExtension()
*/
public String getSourceExtension() {
String sourceExtension;
try {
sourceExtension = getMetaData(METADATA_NAMESPACE).getFirstValue(METADATA_EXTENSION);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (sourceExtension == null) {
getLogger().warn(
"No source extension for document [" + this + "]. The extension \""
+ defaultSourceExtension + "\" will be used as default!");
sourceExtension = defaultSourceExtension;
}
return sourceExtension;
}
/**
* Sets the extension of the file in the URL.
* @param _extension A string.
*/
protected void setExtension(String _extension) {
checkWritability();
assert _extension != null;
Assert.isTrue("Extension doesn't start with a dot", !_extension.startsWith("."));
this.extension = _extension;
}
/**
* @see org.apache.lenya.cms.publication.Document#exists()
*/
public boolean exists() throws DocumentException {
try {
return getRepositoryNode().exists();
} catch (RepositoryException e) {
throw new DocumentException(e);
}
}
/**
* @see org.apache.lenya.cms.publication.Document#existsInAnyLanguage()
*/
public boolean existsInAnyLanguage() throws DocumentException {
String[] languages = getLanguages();
if (languages.length > 0) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(
"Document (" + this + ") exists in at least one language: "
+ languages.length);
}
String[] allLanguages = getPublication().getLanguages();
if (languages.length == allLanguages.length)
// TODO: This is not entirely true, because the publication
// could assume the
// languages EN and DE, but the document could exist for the
// languages DE and FR!
if (getLogger().isDebugEnabled()) {
getLogger().debug(
"Document (" + this
+ ") exists even in all languages of this publication");
}
return true;
} else {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Document (" + this + ") does NOT exist in any language");
}
return false;
}
}
public DocumentIdentifier getIdentifier() {
return this.identifier;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object object) {
if (getClass().isInstance(object)) {
DocumentImpl document = (DocumentImpl) object;
return document.getIdentifier().equals(getIdentifier());
}
return false;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
String key = getPublication().getId() + ":" + getPublication().getServletContext() + ":"
+ getArea() + ":" + getUUID() + ":" + getLanguage();
return key.hashCode();
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return getIdentifier().toString();
}
/**
* @see org.apache.lenya.cms.publication.Document#getFactory()
*/
public DocumentFactory getFactory() {
return this.factory;
}
/**
* @see org.apache.lenya.cms.publication.Document#getCanonicalWebappURL()
*/
public String getCanonicalWebappURL() {
return "/" + getPublication().getId() + "/" + getArea() + getCanonicalDocumentURL();
}
/**
* @see org.apache.lenya.cms.publication.Document#getCanonicalDocumentURL()
*/
public String getCanonicalDocumentURL() {
try {
DocumentBuilder builder = getPublication().getDocumentBuilder();
String webappUrl = builder.buildCanonicalUrl(getFactory(), getLocator());
String prefix = "/" + getPublication().getId() + "/" + getArea();
return webappUrl.substring(prefix.length());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @see org.apache.lenya.cms.publication.Document#accept(org.apache.lenya.cms.publication.util.DocumentVisitor)
*/
public void accept(DocumentVisitor visitor) throws PublicationException {
visitor.visitDocument(this);
}
/**
* @see org.apache.lenya.cms.publication.Document#delete()
*/
public void delete() throws DocumentException {
if (hasLink()) {
throw new DocumentException("Can't delete document [" + this
+ "], it's still referenced in the site structure.");
}
try {
getRepositoryNode().delete();
} catch (Exception e) {
throw new DocumentException(e);
}
}
protected static final String IDENTIFIABLE_TYPE = "document";
private ResourceType resourceType;
/**
* Convenience method to read the document's resource type from the meta-data.
* @see Document#getResourceType()
*/
public ResourceType getResourceType() throws DocumentException {
if (this.resourceType == null) {
ServiceSelector selector = null;
try {
String name = getMetaData(METADATA_NAMESPACE).getFirstValue(METADATA_RESOURCE_TYPE);
if (name == null) {
throw new DocumentException("No resource type defined for document [" + this
+ "]!");
}
selector = (ServiceSelector) this.manager.lookup(ResourceType.ROLE + "Selector");
this.resourceType = (ResourceType) selector.select(name);
} catch (Exception e) {
throw new DocumentException(e);
}
}
return this.resourceType;
}
public MetaData getMetaData(String namespaceUri) throws MetaDataException {
return getContentHolder().getMetaData(namespaceUri);
}
public String[] getMetaDataNamespaceUris() throws MetaDataException {
return getContentHolder().getMetaDataNamespaceUris();
}
public String getMimeType() throws DocumentException {
try {
String mimeType = getMetaData(METADATA_NAMESPACE).getFirstValue(METADATA_MIME_TYPE);
if (mimeType == null) {
mimeType = "";
}
return mimeType;
} catch (MetaDataException e) {
throw new DocumentException(e);
}
}
public long getContentLength() throws DocumentException {
try {
return getContentHolder().getContentLength();
} catch (RepositoryException e) {
throw new DocumentException(e);
}
}
public void setMimeType(String mimeType) throws DocumentException {
checkWritability();
try {
getMetaData(METADATA_NAMESPACE).setValue(METADATA_MIME_TYPE, mimeType);
} catch (MetaDataException e) {
throw new DocumentException(e);
}
}
public DocumentLocator getLocator() {
SiteStructure structure = area().getSite();
if (!structure.containsByUuid(getUUID(), getLanguage())) {
throw new RuntimeException("The document [" + this
+ "] is not referenced in the site structure.");
}
try {
return DocumentLocator.getLocator(getPublication().getId(), getArea(), structure
.getByUuid(getUUID(), getLanguage()).getNode().getPath(), getLanguage());
} catch (SiteException e) {
throw new RuntimeException(e);
}
}
public String getPath() throws DocumentException {
return getLink().getNode().getPath();
}
public boolean existsAreaVersion(String area) {
String sourceUri = getSourceURI(getPublication(), area, getUUID(), getLanguage());
try {
return SourceUtil.exists(sourceUri, this.manager);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean existsTranslation(String language) {
return area().contains(getUUID(), language);
}
public Document getAreaVersion(String area) throws DocumentException {
try {
return getFactory().get(getPublication(), area, getUUID(), getLanguage());
} catch (DocumentBuildException e) {
throw new DocumentException(e);
}
}
public Document getTranslation(String language) throws DocumentException {
try {
return getFactory().get(getPublication(), getArea(), getUUID(), language);
} catch (DocumentBuildException e) {
throw new DocumentException(e);
}
}
private Node repositoryNode;
/**
* @see org.apache.lenya.cms.publication.Document#getRepositoryNode()
*/
public Node getRepositoryNode() {
if (this.repositoryNode == null) {
this.repositoryNode = getRepositoryNode(this.manager, getFactory(), getSourceURI());
}
return this.repositoryNode;
}
protected ContentHolder getContentHolder() {
Node node = getRepositoryNode();
if (isRevisionSpecified()) {
try {
return node.getHistory().getRevision(revision);
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
} else {
return node;
}
}
protected static Node getRepositoryNode(ServiceManager manager, DocumentFactory docFactory,
String sourceUri) {
Session session = docFactory.getSession();
NodeFactory factory = null;
try {
factory = (NodeFactory) manager.lookup(NodeFactory.ROLE);
return (Node) session.getRepositoryItem(factory, sourceUri);
} catch (Exception e) {
throw new RuntimeException("Creating repository node failed: ", e);
} finally {
if (factory != null) {
manager.release(factory);
}
}
}
/**
* @see org.apache.lenya.cms.publication.Document#getSourceURI()
*/
public String getSourceURI() {
return getSourceURI(getPublication(), getArea(), getUUID(), getLanguage());
}
protected static String getSourceURI(Publication pub, String area, String uuid, String language) {
String path = pub.getPathMapper().getPath(uuid, language);
return pub.getSourceURI() + "/content/" + area + "/" + path;
}
public boolean existsVersion(String area, String language) {
String sourceUri = getSourceURI(getPublication(), area, getUUID(), language);
try {
return SourceUtil.exists(sourceUri, this.manager);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Document getVersion(String area, String language) throws DocumentException {
try {
return getFactory().get(getPublication(), area, getUUID(), language);
} catch (DocumentBuildException e) {
throw new DocumentException(e);
}
}
public Link getLink() throws DocumentException {
SiteStructure structure = area().getSite();
try {
if (structure.containsByUuid(getUUID(), getLanguage())) {
return structure.getByUuid(getUUID(), getLanguage());
} else {
throw new DocumentException("The document [" + this
+ "] is not referenced in the site structure [" + structure + "].");
}
} catch (Exception e) {
throw new DocumentException(e);
}
}
public boolean hasLink() {
return area().getSite().containsByUuid(getUUID(), getLanguage());
}
public Area area() {
try {
return getPublication().getArea(getArea());
} catch (PublicationException e) {
throw new RuntimeException(e);
}
}
public void setResourceType(ResourceType resourceType) {
checkWritability();
Assert.notNull("resource type", resourceType);
try {
MetaData meta = getMetaData(DocumentImpl.METADATA_NAMESPACE);
meta.setValue(DocumentImpl.METADATA_RESOURCE_TYPE, resourceType.getName());
} catch (MetaDataException e) {
throw new RuntimeException(e);
}
}
public void setSourceExtension(String extension) {
checkWritability();
Assert.notNull("extension", extension);
Assert.isTrue("extension doesn't start with a dot", !extension.startsWith("."));
try {
MetaData meta = getMetaData(DocumentImpl.METADATA_NAMESPACE);
meta.setValue(DocumentImpl.METADATA_EXTENSION, extension);
} catch (MetaDataException e) {
throw new RuntimeException(e);
}
}
public OutputStream getOutputStream() {
checkWritability();
try {
return getRepositoryNode().getOutputStream();
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
}
protected void checkWritability() {
if (isRevisionSpecified()) {
throw new UnsupportedOperationException();
}
}
protected boolean isRevisionSpecified() {
return this.revision != -1;
}
public InputStream getInputStream() {
try {
return getRepositoryNode().getInputStream();
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
}
public Session getSession() {
return getFactory().getSession();
}
public int getRevisionNumber() {
if (!isRevisionSpecified()) {
throw new UnsupportedOperationException(
"This is not a particular revision of the document [" + this + "].");
}
return this.revision;
}
}