move all document related classes from publication to document
git-svn-id: https://svn.apache.org/repos/asf/lenya/trunk@1034527 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DefaultDocumentBuilder.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DefaultDocumentBuilder.java
new file mode 100644
index 0000000..bd3ad6e
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DefaultDocumentBuilder.java
@@ -0,0 +1,208 @@
+/*
+ * 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.net.MalformedURLException;
+
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.avalon.framework.thread.ThreadSafe;
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.lenya.cms.site.SiteNode;
+
+/**
+ * Default document builder implementation.
+ *
+ * @version $Id$
+ */
+public class DefaultDocumentBuilder extends AbstractLogEnabled implements DocumentBuilder,
+ Serviceable, ThreadSafe {
+
+ /**
+ * Ctor.
+ */
+ public DefaultDocumentBuilder() {
+ }
+
+ /**
+ * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
+ */
+ public void service(ServiceManager manager) {
+ this.manager = manager;
+ }
+
+ protected ServiceManager manager;
+
+ /**
+ * Removes all "."-separated extensions from a URL (e.g.,
+ * <code>/foo.print.html</code> is transformed to <code>/foo</code>).
+ * @param url The URL to trim.
+ * @return A URL string.
+ */
+ protected String removeExtensions(String url) {
+ int dotIndex = url.indexOf(".");
+ if (dotIndex > -1) {
+ url = url.substring(0, dotIndex);
+ }
+ return url;
+ }
+
+ /**
+ * Returns the language of a URL.
+ * @param urlWithoutSuffix The URL without the suffix.
+ * @return A string.
+ */
+ protected String getLanguage(String urlWithoutSuffix) {
+
+ String language = "";
+ String url = urlWithoutSuffix;
+
+ int languageSeparatorIndex = url.lastIndexOf("_");
+ if (languageSeparatorIndex > -1) {
+ String suffix = url.substring(languageSeparatorIndex + 1);
+ if (suffix.length() <= 5) {
+ language = suffix;
+ }
+ }
+ return language;
+ }
+
+ /**
+ * Returns the extension of a URL.
+ * @param url The URL.
+ * @return The extension.
+ */
+ protected String getExtension(String url) {
+ int startOfSuffix = url.lastIndexOf('.');
+ String suffix = "";
+
+ if ((startOfSuffix > -1) && !url.endsWith(".")) {
+ suffix = url.substring(startOfSuffix + 1);
+ }
+
+ return suffix;
+ }
+
+ public boolean isDocument(Session session, String url) {
+ try {
+ DocumentLocator locator = getLocatorWithoutCheck(session, url);
+ if (locator != null) {
+ Publication pub = session.getPublication(locator.getPublicationId());
+ String path = locator.getPath();
+ Area area = pub.getArea(locator.getArea());
+ if (area.getSite().contains(path)) {
+ SiteNode node = area.getSite().getNode(path);
+ if (node.hasLink(locator.getLanguage())) {
+ return true;
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Builds the canonical document URL.
+ * @param session The document factory.
+ * @param locator The document locator.
+ * @return A string.
+ */
+ protected String buildCanonicalDocumentUrl(Session session, DocumentLocator locator) {
+
+ String languageSuffix = "";
+ String language = locator.getLanguage();
+
+ Publication pub = session.getPublication(locator.getPublicationId());
+
+ if (!language.equals(pub.getDefaultLanguage())) {
+ languageSuffix = "_" + language;
+ }
+
+ return locator.getPath() + languageSuffix + ".html";
+ }
+
+ public String buildCanonicalUrl(Session session, DocumentLocator doc) {
+
+ String documentUrl = buildCanonicalDocumentUrl(session, doc);
+ String url = "/" + doc.getPublicationId() + "/" + doc.getArea() + documentUrl;
+ return url;
+ }
+
+ public DocumentLocator getLocator(Session session, String webappUrl) throws MalformedURLException {
+
+ DocumentLocator locator = getLocatorWithoutCheck(session, webappUrl);
+ if (locator == null) {
+ throw new ResourceNotFoundException("The webapp URL [" + webappUrl
+ + "] does not refer to a document!");
+ }
+ return locator;
+ }
+
+ /**
+ * Creates a document locator for a webapp URL without checking if the
+ * webapp URL refers to a locator first.
+ * @param session The document factory.
+ * @param webappUrl The webapp URL.
+ * @return A document locator or <code>null</code> if the URL doesn't
+ * refer to a locator.
+ * @throws MalformedURLException if the URL is not a webapp URL.
+ */
+ protected DocumentLocator getLocatorWithoutCheck(Session session, String webappUrl) throws MalformedURLException {
+
+ if (!webappUrl.startsWith("/")) {
+ return null;
+ }
+ if (webappUrl.substring(1).split("/").length < 3) {
+ return null;
+ }
+
+ URLInformation info = new URLInformation(webappUrl);
+
+ Publication publication = session.getPublication(info.getPublicationId());
+ String documentURL = info.getDocumentUrl();
+ documentURL = removeExtensions(documentURL);
+
+ String language = getLanguage(documentURL);
+ String fullLanguage = "".equals(language) ? "" : ("_" + language);
+ documentURL = documentURL.substring(0, documentURL.length() - fullLanguage.length());
+
+ if ("".equals(language)) {
+ language = publication.getDefaultLanguage();
+ }
+
+ String path = documentURL;
+
+ if (!path.startsWith("/")) {
+ throw new MalformedURLException("Path [" + path + "] does not start with '/'!");
+ }
+
+ return DocumentLocator.getLocator(publication.getId(), info.getArea(), path, language);
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentBuilder#isValidDocumentName(java.lang.String)
+ */
+ public boolean isValidDocumentName(String documentName) {
+ return documentName.matches("[a-zA-Z0-9\\-]+");
+ }
+
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DefaultDocumentIdToPathMapper.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DefaultDocumentIdToPathMapper.java
new file mode 100644
index 0000000..1086670
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DefaultDocumentIdToPathMapper.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+/**
+ * Default DocumentIdToPathMapper implementation.
+ *
+ * @version $Id$
+ */
+public class DefaultDocumentIdToPathMapper implements DocumentIdToPathMapper {
+
+ /**
+ * The file name.
+ */
+ public static final String BASE_FILENAME_PREFIX = "index";
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentIdToPathMapper#getPath(java.lang.String,
+ * java.lang.String)
+ */
+ public String getPath(String uuid, String language) {
+ if (uuid.startsWith("/")) {
+ return uuid.substring(1) + "/" + getFilename(language);
+ }
+ else {
+ return uuid + "/" + language;
+ }
+ }
+
+ /**
+ * Constructs the filename for a given language.
+ *
+ * @param language The language.
+ * @return A string value.
+ */
+ protected String getFilename(String language) {
+ String languageSuffix = "";
+ if (language != null && !"".equals(language)) {
+ languageSuffix = "_" + language;
+ }
+ return BASE_FILENAME_PREFIX + languageSuffix;
+ }
+
+ /**
+ * Returns the language for a certain file
+ *
+ * @param file the document file
+ *
+ * @return the language for the given document file or null if the file has no language.
+ */
+ public String getLanguage(File file) {
+ String fileName = file.getName();
+ String language = null;
+
+ int lastDotIndex = fileName.lastIndexOf(".");
+ String suffix = fileName.substring(lastDotIndex);
+
+ // check if the file is of the form index.html or index_en.html
+
+ if (fileName.startsWith(BASE_FILENAME_PREFIX) && fileName.endsWith(suffix)) {
+ String languageSuffix = fileName.substring(BASE_FILENAME_PREFIX.length(),
+ fileName.indexOf(suffix));
+ if (languageSuffix.length() > 0) {
+ // trim the leading '_'
+ language = languageSuffix.substring(1);
+ }
+ }
+ return language;
+ }
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/Document.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/Document.java
new file mode 100644
index 0000000..0498b21
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/Document.java
@@ -0,0 +1,307 @@
+/*
+ * 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.InputStream;
+import java.io.OutputStream;
+import java.util.Date;
+
+import org.apache.lenya.cms.metadata.MetaDataOwner;
+import org.apache.lenya.cms.publication.util.DocumentVisitor;
+import org.apache.lenya.cms.site.Link;
+
+/**
+ * A CMS document.
+ * @version $Id$
+ */
+public interface Document extends Node, MetaDataOwner {
+
+ /**
+ * The document namespace URI.
+ */
+ String NAMESPACE = "http://apache.org/cocoon/lenya/document/1.0";
+
+ /**
+ * The default namespace prefix.
+ */
+ String DEFAULT_PREFIX = "lenya";
+
+ /**
+ * The transactionable type for document objects.
+ */
+ String TRANSACTIONABLE_TYPE = "document";
+
+ /**
+ * <code>DOCUMENT_META_SUFFIX</code> The suffix for document meta Uris
+ */
+ final String DOCUMENT_META_SUFFIX = ".meta";
+
+ /**
+ * Returns the date at which point the requested document is considered expired
+ * @return a string in RFC 1123 date format
+ * @throws DocumentException if an error occurs.
+ */
+ Date getExpires() throws DocumentException;
+
+ /**
+ * Returns the document name of this document.
+ * @return the document-name of this document.
+ */
+ String getName();
+
+ /**
+ * Returns the publication this document belongs to.
+ * @return A publication object.
+ */
+ Publication getPublication();
+
+ /**
+ * Returns the canonical web application URL.
+ * @return A string.
+ */
+ String getCanonicalWebappURL();
+
+ /**
+ * Returns the canonical document URL.
+ * @return A string.
+ */
+ String getCanonicalDocumentURL();
+
+ /**
+ * Returns the language of this document.
+ * Each document has one language associated to it.
+ * @return A string denoting the language.
+ */
+ String getLanguage();
+
+ /**
+ * Returns all the languages this document is available in.
+ * A document has one associated language (@see Document#getLanguage)
+ * but there are possibly a number of other languages for which a
+ * document with the same document-uuid is also available in.
+ *
+ * @return An array of strings denoting the languages.
+ */
+ String[] getLanguages();
+
+ /**
+ * Returns the date of the last modification of this document.
+ * @return A date denoting the date of the last modification.
+ * @throws DocumentException if an error occurs.
+ */
+ long getLastModified() throws DocumentException;
+
+ /**
+ * Returns the area this document belongs to.
+ * @return The area.
+ */
+ String getArea();
+
+ /**
+ * Returns the extension in the URL without the dot.
+ * @return A string.
+ */
+ String getExtension();
+
+ /**
+ * Returns the UUID.
+ * @return A string.
+ */
+ String getUUID();
+
+ /**
+ * Check if a document with the given document-uuid, language and in the given
+ * area actually exists.
+ *
+ * @return true if the document exists, false otherwise
+ */
+ boolean exists();
+
+ /**
+ * Check if a document exists with the given document-uuid and the given area
+ * independently of the given language.
+ *
+ * @return true if a document with the given document-uuid and area exists, false otherwise
+ */
+ boolean existsInAnyLanguage();
+
+ /**
+ * Returns the URI to resolve the document's source.
+ * The source can only be used for read-only access.
+ * For write access, use {@link #getOutputStream()}.
+ * @return A string.
+ */
+ String getSourceURI();
+
+ /**
+ * @return The output stream to write the document content to.
+ */
+ OutputStream getOutputStream();
+
+ /**
+ * Accepts a document visitor.
+ * @param visitor The visitor.
+ * @throws Exception if an error occurs.
+ */
+ void accept(DocumentVisitor visitor) throws Exception;
+
+ /**
+ * Deletes the document.
+ * @throws DocumentException if an error occurs.
+ */
+ void delete() throws DocumentException;
+
+ /**
+ * @return The resource type of this document (formerly known as doctype)
+ * @throws DocumentException if the resource type has not been set.
+ */
+ ResourceType getResourceType() throws DocumentException;
+
+ /**
+ * @param resourceType The resource type of this document.
+ */
+ void setResourceType(ResourceType resourceType);
+
+ /**
+ * @return The source extension used by this document, without the dot.
+ */
+ String getSourceExtension();
+
+ /**
+ * @param extension The source extension used by this document, without the dot.
+ */
+ void setSourceExtension(String extension);
+
+ /**
+ * Sets the mime type of this document.
+ * @param mimeType The mime type.
+ */
+ void setMimeType(String mimeType);
+
+ /**
+ * @return The mime type of this document.
+ * @throws DocumentException if the mime type has not been set.
+ */
+ String getMimeType() throws DocumentException;
+
+ /**
+ * @return The content length of the document.
+ */
+ long getContentLength();
+
+ /**
+ * @return The document identifier for this document.
+ */
+ DocumentIdentifier getIdentifier();
+
+ /**
+ * This is a shortcut to getLink().getNode().getPath().
+ * @return The path of this document in the site structure.
+ * @throws DocumentException if the document is not linked in the site structure.
+ */
+ String getPath() throws DocumentException;
+
+ /**
+ * Checks if a certain translation (language version) of this document exists.
+ * @param language The language.
+ * @return A boolean value.
+ */
+ boolean existsTranslation(String language);
+
+ /**
+ * Returns a certain translation (language version) of this document.
+ * @param language The language.
+ * @return A document.
+ * @throws ResourceNotFoundException if the language version doesn't exist.
+ */
+ Document getTranslation(String language) throws ResourceNotFoundException;
+
+ /**
+ * Checks if this document exists in a certain area.
+ * @param area The area.
+ * @return A boolean value.
+ */
+ boolean existsAreaVersion(String area);
+
+ /**
+ * Returns the document in a certain area.
+ * @param area The area.
+ * @return A document.
+ * @throws ResourceNotFoundException if the area version doesn't exist.
+ */
+ Document getAreaVersion(String area) throws ResourceNotFoundException;
+
+ /**
+ * Checks if a translation of this document exists in a certain area.
+ * @param area The area.
+ * @param language The language.
+ * @return A boolean value.
+ */
+ boolean existsVersion(String area, String language);
+
+ /**
+ * Returns a translation of this document in a certain area.
+ * @param area The area.
+ * @param language The language.
+ * @return A document.
+ * @throws ResourceNotFoundException if the area version doesn't exist.
+ */
+ Document getVersion(String area, String language) throws ResourceNotFoundException;
+
+ /**
+ * @return A document locator.
+ */
+ DocumentLocator getLocator();
+
+ /**
+ * @return The link to this document in the site structure.
+ * @throws DocumentException if the document is not referenced in the site structure.
+ */
+ Link getLink() throws DocumentException;
+
+ /**
+ * @return The area the document belongs to.
+ */
+ Area area();
+
+ /**
+ * @return if the document is linked in the site structure.
+ */
+ boolean hasLink();
+
+ /**
+ * @return The input stream to obtain the document's content.
+ */
+ InputStream getInputStream();
+
+ /**
+ * @param i The revision number.
+ * @return A revision.
+ * @throws RepositoryException if the revision doesn't exist.
+ */
+ Document getRevision(int i) throws RepositoryException;
+
+ /**
+ * @return The revision number of this document.
+ */
+ int getRevisionNumber();
+
+ History getHistory();
+
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentBuildException.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentBuildException.java
new file mode 100644
index 0000000..59ebdb8
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentBuildException.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+/**
+ * Document build exception.
+ *
+ * @version $Id$
+ */
+public class DocumentBuildException extends PublicationException {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ */
+ public DocumentBuildException() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ * @param message A message.
+ */
+ public DocumentBuildException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ * @param cause The cause of the exception.
+ */
+ public DocumentBuildException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Constructor.
+ * @param message A message.
+ * @param cause The cause of the exception.
+ */
+ public DocumentBuildException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentBuilder.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentBuilder.java
new file mode 100644
index 0000000..7a99efa
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentBuilder.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ *
+ */
+
+/* $Id$ */
+
+package org.apache.lenya.cms.publication;
+
+import java.net.MalformedURLException;
+
+/**
+ * A document builder builds a document from a URL.
+ */
+public interface DocumentBuilder {
+
+ /**
+ * The Avalon role.
+ */
+ String ROLE = DocumentBuilder.class.getName();
+
+ /**
+ * Returns a document for a web application URL.
+ * @param factory The factory.
+ * @param webappUrl The web application URL.
+ * @return A document identifier.
+ * @throws MalformedURLException if the URL is not a webapp URL.
+ */
+ DocumentLocator getLocator(Session session, String webappUrl) throws MalformedURLException;
+
+ /**
+ * Checks if an URL corresponds to a CMS document.
+ * @param factory The document factory.
+ * @param url The URL of the form /{publication-id}/...
+ * @return A boolean value.
+ * @throws DocumentBuildException when something went wrong.
+ */
+ boolean isDocument(Session session, String url);
+
+ /**
+ * Builds an URL corresponding to a CMS document.
+ * @param factory The document factory.
+ * @param locator The locator.
+ * @return a String The corresponding URL.
+ */
+ String buildCanonicalUrl(Session session, DocumentLocator locator);
+
+ /**
+ * Checks if a document name is valid.
+ * @param documentName The document name.
+ * @return A boolean value.
+ */
+ boolean isValidDocumentName(String documentName);
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentDoesNotExistException.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentDoesNotExistException.java
new file mode 100644
index 0000000..7f4f15c
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentDoesNotExistException.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ *
+ */
+
+/* $Id$ */
+
+package org.apache.lenya.cms.publication;
+
+/**
+ * Document does not exist exception
+ */
+public class DocumentDoesNotExistException extends DocumentException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new DocumentDoesNotExistException
+ *
+ */
+ public DocumentDoesNotExistException() {
+ super();
+ }
+
+ /**
+ * Creates a new DocumentDoesNotExistException
+ * @param message the exception message
+ */
+ public DocumentDoesNotExistException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new DocumentDoesNotExistException
+ * @param message the exception message
+ * @param cause the cause of the exception
+ */
+ public DocumentDoesNotExistException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Creates a new DocumentDoesNotExistException
+ * @param cause the cause of the exception
+ */
+ public DocumentDoesNotExistException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentException.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentException.java
new file mode 100644
index 0000000..35ab77d
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentException.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ *
+ */
+
+/* $Id$ */
+
+package org.apache.lenya.cms.publication;
+
+/**
+ * Document exception
+ */
+public class DocumentException extends PublicationException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new DocumentException
+ *
+ */
+ public DocumentException() {
+ super();
+ }
+
+ /**
+ * Creates a new DocumentException
+ *
+ * @param message the exception message
+ */
+ public DocumentException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new DocumentException
+ *
+ * @param message the exception message
+ * @param cause the cause of the exception
+ */
+ public DocumentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Creates a new DocumentException
+ *
+ * @param cause the cause of the exception
+ */
+ public DocumentException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactory.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactory.java
new file mode 100644
index 0000000..048b8ae
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactory.java
@@ -0,0 +1,108 @@
+/*
+ * 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;
+
+/**
+ * A DocumentIdentityMap avoids the multiple instanciation of a document object.
+ *
+ * @version $Id$
+ */
+public interface DocumentFactory {
+
+ /**
+ * Returns a document.
+ * @param identifier The identifier of the document.
+ * @return A document.
+ * @throws ResourceNotFoundException if the document does not exist.
+ */
+ Document get(DocumentIdentifier identifier) throws ResourceNotFoundException;
+
+ /**
+ * Returns a document.
+ * @param publication The publication.
+ * @param area The area.
+ * @param uuid The document ID.
+ * @param language The language.
+ * @return A document.
+ * @throws ResourceNotFoundException if the document does not exist.
+ */
+ Document get(Publication publication, String area, String uuid, String language)
+ throws ResourceNotFoundException;
+
+ /**
+ * Returns a revision of a document.
+ * @param publication The publication.
+ * @param area The area.
+ * @param uuid The document ID.
+ * @param language The language.
+ * @param revision The revision..
+ * @return A document.
+ * @throws ResourceNotFoundException if the document does not exist.
+ */
+ Document get(Publication publication, String area, String uuid, String language, int revision)
+ throws ResourceNotFoundException;
+
+ /**
+ * Returns the document identified by a certain web application URL.
+ * @param webappUrl The web application URL.
+ * @return A document.
+ * @throws ResourceNotFoundException if an error occurs.
+ */
+ Document getFromURL(String webappUrl) throws ResourceNotFoundException;
+
+ /**
+ * Builds a document for the default language.
+ * @param publication The publication.
+ * @param area The area.
+ * @param uuid The document UUID.
+ * @return A document.
+ * @throws ResourceNotFoundException if an error occurs.
+ */
+ Document get(Publication publication, String area, String uuid)
+ throws ResourceNotFoundException;
+
+ /**
+ * Checks if a webapp URL represents a document.
+ * @param webappUrl A web application URL.
+ * @return A boolean value.
+ */
+ boolean isDocument(String webappUrl);
+
+ /**
+ * @return The session.
+ */
+ Session getSession();
+
+ /**
+ * @param id The publication ID.
+ * @return A publication.
+ * @throws PublicationException if the publication does not exist.
+ */
+ Publication getPublication(String id) throws PublicationException;
+
+ /**
+ * @return All publication IDs.
+ */
+ String[] getPublicationIds();
+
+ /**
+ * @param id The publication ID.
+ * @return If a publication with this ID exists.
+ */
+ boolean existsPublication(String id);
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactoryBuilder.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactoryBuilder.java
new file mode 100644
index 0000000..c0f333c
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactoryBuilder.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Document factory builder.
+ */
+public interface DocumentFactoryBuilder {
+
+ /**
+ * Creates a new document factory.
+ * @param session The session.
+ * @return A document identity map.
+ */
+ DocumentFactory createDocumentFactory(Session session);
+
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactoryBuilderImpl.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactoryBuilderImpl.java
new file mode 100644
index 0000000..c92610c
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactoryBuilderImpl.java
@@ -0,0 +1,79 @@
+/*
+ * 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 org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.lenya.cms.metadata.MetaDataCache;
+import org.apache.lenya.cms.repository.NodeFactory;
+
+/**
+ * Document factory builder implementation.
+ */
+public class DocumentFactoryBuilderImpl extends AbstractLogEnabled implements
+ DocumentFactoryBuilder {
+
+ private PublicationManager pubManager;
+ private MetaDataCache metaDataCache;
+ private SourceResolver sourceResolver;
+ private NodeFactory nodeFactory;
+ private ResourceTypeResolver resourceTypeResolver;
+
+ public DocumentFactory createDocumentFactory(Session session) {
+ DocumentFactoryImpl factory = new DocumentFactoryImpl(session);
+ factory.setMetaDataCache(getMetaDataCache());
+ factory.setPublicationManager(getPublicationManager());
+ factory.setSourceResolver(getSourceResolver());
+ factory.setNodeFactory(this.nodeFactory);
+ factory.setResourceTypeResolver(this.resourceTypeResolver);
+ return factory;
+ }
+
+ public void setPublicationManager(PublicationManager pubManager) {
+ this.pubManager = pubManager;
+ }
+
+ protected PublicationManager getPublicationManager() {
+ return this.pubManager;
+ }
+
+ public void setMetaDataCache(MetaDataCache metaDataCache) {
+ this.metaDataCache = metaDataCache;
+ }
+
+ protected MetaDataCache getMetaDataCache() {
+ return metaDataCache;
+ }
+
+ public SourceResolver getSourceResolver() {
+ return sourceResolver;
+ }
+
+ public void setSourceResolver(SourceResolver sourceResolver) {
+ this.sourceResolver = sourceResolver;
+ }
+
+ public void setNodeFactory(NodeFactory nodeFactory) {
+ this.nodeFactory = nodeFactory;
+ }
+
+ public void setResourceTypeResolver(ResourceTypeResolver resourceTypeResolver) {
+ this.resourceTypeResolver = resourceTypeResolver;
+ }
+
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactoryImpl.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactoryImpl.java
new file mode 100644
index 0000000..4e557aa
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentFactoryImpl.java
@@ -0,0 +1,374 @@
+/*
+ * 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.util.Arrays;
+import java.util.StringTokenizer;
+
+import org.apache.commons.lang.Validate;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.lenya.cms.metadata.MetaDataCache;
+import org.apache.lenya.cms.repository.NodeFactory;
+import org.apache.lenya.cms.repository.RepositoryException;
+import org.apache.lenya.cms.repository.RepositoryItem;
+import org.apache.lenya.cms.repository.RepositoryItemFactory;
+import org.apache.lenya.cms.repository.SessionHolder;
+
+/**
+ * A DocumentIdentityMap avoids the multiple instanciation of a document object.
+ *
+ * @version $Id: DocumentIdentityMap.java 264153 2005-08-29 15:11:14Z andreas $
+ */
+public class DocumentFactoryImpl implements DocumentFactory, RepositoryItemFactory {
+
+ private static final Log logger = LogFactory.getLog(DocumentFactoryImpl.class);
+
+ private Session session;
+ private MetaDataCache metaDataCache;
+ private SourceResolver sourceResolver;
+ private NodeFactory nodeFactory;
+ private ResourceTypeResolver resourceTypeResolver;
+
+ /**
+ * @return The session.
+ */
+ public Session getSession() {
+ return this.session;
+ }
+
+ /**
+ * Ctor.
+ * @param session The session to use.
+ */
+ public DocumentFactoryImpl(Session session) {
+ this.session = session;
+ }
+
+ /**
+ * Returns a document.
+ * @param publication The publication.
+ * @param area The area.
+ * @param uuid The document UUID.
+ * @param language The language.
+ * @return A document.
+ * @throws ResourceNotFoundException if an error occurs.
+ */
+ public Document get(Publication publication, String area, String uuid, String language)
+ throws ResourceNotFoundException {
+ return get(publication, area, uuid, language, -1);
+ }
+
+ public Document get(Publication publication, String area, String uuid, String language,
+ int revision) throws ResourceNotFoundException {
+ if (logger.isDebugEnabled())
+ logger.debug(
+ "DocumentIdentityMap::get() called on publication [" + publication.getId()
+ + "], area [" + area + "], UUID [" + uuid + "], language [" + language
+ + "]");
+
+ String key = getKey(publication, area, uuid, language, revision);
+
+ if (logger.isDebugEnabled())
+ logger.debug(
+ "DocumentIdentityMap::get() got key [" + key + "] from DocumentFactory");
+
+ try {
+ return (Document) getRepositorySession().getRepositoryItem(this, key);
+ } catch (RepositoryException e) {
+ throw new ResourceNotFoundException(e);
+ }
+ }
+
+ protected org.apache.lenya.cms.repository.Session getRepositorySession() {
+ SessionHolder holder = (SessionHolder) this.session;
+ return holder.getRepositorySession();
+ }
+
+ /**
+ * Returns the document identified by a certain web application URL.
+ * @param webappUrl The web application URL.
+ * @return A document.
+ * @throws ResourceNotFoundException if an error occurs.
+ */
+ public Document getFromURL(String webappUrl) throws ResourceNotFoundException {
+ String key = getKey(webappUrl);
+ try {
+ return (Document) getRepositorySession().getRepositoryItem(this, key);
+ } catch (RepositoryException e) {
+ throw new ResourceNotFoundException(e);
+ }
+ }
+
+ /**
+ * Builds a clone of a document for another language.
+ * @param document The document to clone.
+ * @param language The language of the target document.
+ * @return A document.
+ * @throws DocumentBuildException if an error occurs.
+ */
+ public Document getLanguageVersion(Document document, String language)
+ throws DocumentBuildException {
+ return get(document.getPublication(), document.getArea(), document.getUUID(), language);
+ }
+
+ /**
+ * Builds a clone of a document for another area.
+ * @param document The document to clone.
+ * @param area The area of the target document.
+ * @return A document.
+ * @throws ResourceNotFoundException if an error occurs.
+ */
+ public Document getAreaVersion(Document document, String area) throws ResourceNotFoundException {
+ return get(document.getPublication(), area, document.getUUID(), document.getLanguage());
+ }
+
+ /**
+ * Builds a document for the default language.
+ * @param publication The publication.
+ * @param area The area.
+ * @param documentId The document ID.
+ * @return A document.
+ * @throws ResourceNotFoundException if an error occurs.
+ */
+ public Document get(Publication publication, String area, String documentId)
+ throws ResourceNotFoundException {
+ return get(publication, area, documentId, publication.getDefaultLanguage());
+ }
+
+ /**
+ * Checks if a string represents a valid document ID.
+ * @param id The string.
+ * @return A boolean value.
+ */
+ public boolean isValidDocumentId(String id) {
+
+ if (!id.startsWith("/")) {
+ return false;
+ }
+
+ String[] snippets = id.split("/");
+
+ if (snippets.length < 2) {
+ return false;
+ }
+
+ for (int i = 1; i < snippets.length; i++) {
+ if (!snippets[i].matches("[a-zA-Z0-9\\-]+")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if a webapp URL represents a document.
+ * @param webappUrl A web application URL.
+ * @return A boolean value.
+ * @throws ResourceNotFoundException if an error occurs.
+ */
+ public boolean isDocument(String webappUrl) throws ResourceNotFoundException {
+ Validate.notNull(webappUrl);
+ try {
+ URLInformation info = new URLInformation(webappUrl);
+ String pubId = info.getPublicationId();
+ String[] pubIds = getPublicationIds();
+ if (pubId != null && Arrays.asList(pubIds).contains(pubId)) {
+ Publication pub = getPublication(pubId);
+ DocumentBuilder builder = pub.getDocumentBuilder();
+ return builder.isDocument(this.session, webappUrl);
+ } else {
+ return false;
+ }
+ } catch (PublicationException e) {
+ throw new ResourceNotFoundException(e);
+ }
+ }
+
+ /**
+ * Builds a document key.
+ * @param publication The publication.
+ * @param area The area.
+ * @param uuid The document UUID.
+ * @param language The language.
+ * @param revision
+ * @return A key.
+ */
+ public String getKey(Publication publication, String area, String uuid, String language,
+ int revision) {
+ Validate.notNull(publication);
+ Validate.notNull(area);
+ Validate.notNull(uuid);
+ Validate.notNull(language);
+ return publication.getId() + ":" + area + ":" + uuid + ":" + language + ":" + revision;
+ }
+
+ /**
+ * Builds a document key.
+ * @param webappUrl The web application URL.
+ * @return A key.
+ */
+ public String getKey(String webappUrl) {
+ Validate.notNull(webappUrl);
+ try {
+ if (!isDocument(webappUrl)) {
+ throw new RuntimeException("No document for URL [" + webappUrl + "] found.");
+ }
+ DocumentLocator locator = getLocator(webappUrl);
+ Publication publication = getPublication(locator.getPublicationId());
+ String area = locator.getArea();
+ String uuid = publication.getArea(area).getSite().getNode(locator.getPath()).getUuid();
+ return getKey(publication, area, uuid, locator.getLanguage(), -1);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected DocumentLocator getLocator(String webappUrl) {
+ DocumentLocator locator;
+ try {
+ URLInformation info = new URLInformation(webappUrl);
+ Publication publication = getPublication(info.getPublicationId());
+ DocumentBuilder builder = publication.getDocumentBuilder();
+ locator = builder.getLocator(this.session, webappUrl);
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return locator;
+ }
+
+ /**
+ * @see org.apache.lenya.transaction.IdentifiableFactory#build(org.apache.lenya.transaction.IdentityMap,
+ * java.lang.String)
+ */
+ public RepositoryItem buildItem(org.apache.lenya.cms.repository.Session session, String key) throws RepositoryException {
+ if (logger.isDebugEnabled())
+ logger.debug("DocumentFactory::build() called with key [" + key + "]");
+
+ StringTokenizer tokenizer = new StringTokenizer(key, ":");
+ String publicationId = tokenizer.nextToken();
+ String area = tokenizer.nextToken();
+ String uuid = tokenizer.nextToken();
+ String language = tokenizer.nextToken();
+ String revisionString = tokenizer.nextToken();
+ int revision = Integer.valueOf(revisionString).intValue();
+
+ DocumentImpl document;
+ try {
+ Publication publication = getPublication(publicationId);
+ DocumentBuilder builder = publication.getDocumentBuilder();
+ DocumentIdentifier identifier = new DocumentIdentifier(publicationId, area, uuid,
+ language);
+ document = buildDocument(identifier, revision, builder);
+ } catch (Exception e) {
+ throw new RepositoryException(e);
+ }
+ if (logger.isDebugEnabled())
+ logger.debug("DocumentFactory::build() done.");
+
+ return document;
+ }
+
+ protected DocumentImpl buildDocument(DocumentIdentifier identifier, int revision,
+ DocumentBuilder builder) throws DocumentBuildException {
+ return createDocument(identifier, revision, builder);
+ }
+
+ /**
+ * Creates a new document object. Override this method to create specific document objects,
+ * e.g., for different document IDs.
+ * @param identifier The identifier.
+ * @param revision The revision or -1 for the latest revision.
+ * @param builder The document builder.
+ * @return A document.
+ * @throws DocumentBuildException when something went wrong.
+ */
+ protected DocumentImpl createDocument(DocumentIdentifier identifier, int revision,
+ DocumentBuilder builder) throws DocumentBuildException {
+ DocumentImpl doc = new DocumentImpl(session, identifier, revision);
+ doc.setMetaDataCache(getMetaDataCache());
+ doc.setSourceResolver(getSourceResolver());
+ doc.setNodeFactory(this.nodeFactory);
+ doc.setResourceTypeResolver(this.resourceTypeResolver);
+ return doc;
+ }
+
+ public Document get(DocumentIdentifier identifier) throws ResourceNotFoundException {
+ try {
+ Publication pub = getPublication(identifier.getPublicationId());
+ return get(pub, identifier.getArea(), identifier.getUUID(), identifier.getLanguage());
+ } catch (PublicationException e) {
+ throw new ResourceNotFoundException(e);
+ }
+ }
+
+ public String getItemType() {
+ return Document.TRANSACTIONABLE_TYPE;
+ }
+
+ public Publication getPublication(String id) throws PublicationException {
+ return getPublicationManager().getPublication(this, id);
+ }
+
+ public String[] getPublicationIds() {
+ return getPublicationManager().getPublicationIds();
+ }
+
+ private PublicationManager pubManager;
+
+ protected void setPublicationManager(PublicationManager pubManager) {
+ this.pubManager = pubManager;
+ }
+
+ protected PublicationManager getPublicationManager() {
+ return this.pubManager;
+ }
+
+ public boolean existsPublication(String id) {
+ return Arrays.asList(getPublicationManager().getPublicationIds()).contains(id);
+ }
+
+ protected MetaDataCache getMetaDataCache() {
+ return metaDataCache;
+ }
+
+ public void setMetaDataCache(MetaDataCache metaDataCache) {
+ this.metaDataCache = metaDataCache;
+ }
+
+ public SourceResolver getSourceResolver() {
+ return sourceResolver;
+ }
+
+ public void setSourceResolver(SourceResolver sourceResolver) {
+ this.sourceResolver = sourceResolver;
+ }
+
+ public void setNodeFactory(NodeFactory nodeFactory) {
+ Validate.notNull(nodeFactory, "node factory");
+ this.nodeFactory = nodeFactory;
+ }
+
+ public void setResourceTypeResolver(ResourceTypeResolver resourceTypeResolver) {
+ this.resourceTypeResolver = resourceTypeResolver;
+ }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentIdToPathMapper.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentIdToPathMapper.java
new file mode 100644
index 0000000..43fee73
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentIdToPathMapper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *
+ */
+
+/* $Id$ */
+
+package org.apache.lenya.cms.publication;
+
+/**
+ * Document Id to Path mapper interface
+ */
+public interface DocumentIdToPathMapper {
+
+ /**
+ * Compute the document-path for a given publication, area and document-uuid. The file separator
+ * is the slash (/).
+ *
+ * @param uuid the UUID of the document
+ * @param language the language of the document
+ *
+ * @return the path to the document, without publication ID and area
+ */
+ String getPath(String uuid, String language);
+
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentIdentifier.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentIdentifier.java
new file mode 100644
index 0000000..38cac3e
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentIdentifier.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+/**
+ * Value object to identify documents.
+ */
+public class DocumentIdentifier {
+
+ private String publicationId;
+ private String area;
+ private String language;
+ private String uuid;
+
+ /**
+ * Ctor.
+ * @param pubId The publication ID.
+ * @param area The area.
+ * @param uuid The document UUID.
+ * @param language The language.
+ */
+ public DocumentIdentifier(String pubId, String area, String uuid, String language) {
+
+ if (uuid.startsWith("/") && uuid.split("-").length == 4) {
+ throw new IllegalArgumentException("The UUID [" + uuid + "] must not begin with a '/'!");
+ }
+ if (uuid.indexOf("/") > 0) {
+ throw new IllegalArgumentException("The UUID [" + uuid
+ + "] must not contain a '/' after the first position!");
+ }
+
+ this.publicationId = pubId;
+ this.area = area;
+ this.language = language;
+ this.uuid = uuid;
+ }
+
+ /**
+ * @return The UUID.
+ */
+ public String getUUID() {
+ return this.uuid;
+ }
+
+ /**
+ * @return The area.
+ */
+ public String getArea() {
+ return area;
+ }
+
+ /**
+ * @return The language.
+ */
+ public String getLanguage() {
+ return language;
+ }
+
+ /**
+ * @return The publication ID.
+ */
+ public String getPublicationId() {
+ return publicationId;
+ }
+
+ public boolean equals(Object obj) {
+ return (obj instanceof DocumentIdentifier) && obj.hashCode() == hashCode();
+ }
+
+ public int hashCode() {
+ return getKey().hashCode();
+ }
+
+ protected String getKey() {
+ return this.publicationId + ":" + this.area + ":" + this.uuid + ":" + this.language;
+ }
+
+ public String toString() {
+ return getKey();
+ }
+
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentImpl.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentImpl.java
new file mode 100644
index 0000000..282b510
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentImpl.java
@@ -0,0 +1,779 @@
+/*
+ * 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.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.lang.Validate;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.lenya.cms.cocoon.source.SourceUtil;
+import org.apache.lenya.cms.metadata.MetaData;
+import org.apache.lenya.cms.metadata.MetaDataCache;
+import org.apache.lenya.cms.metadata.MetaDataException;
+import org.apache.lenya.cms.metadata.MetaDataWrapper;
+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.RepositoryItem;
+import org.apache.lenya.cms.repository.Session;
+import org.apache.lenya.cms.repository.SessionHolder;
+import org.apache.lenya.cms.site.Link;
+import org.apache.lenya.cms.site.SiteException;
+import org.apache.lenya.cms.site.SiteStructure;
+
+/**
+ * A typical CMS document.
+ * @version $Id$
+ */
+public class DocumentImpl implements Document, RepositoryItem {
+
+ private static final Log logger = LogFactory.getLog(DocumentImpl.class);
+
+ private DocumentIdentifier identifier;
+ private org.apache.lenya.cms.publication.Session session;
+ private NodeFactory nodeFactory;
+ private ResourceTypeResolver resourceTypeResolver;
+ 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 session The session the document belongs to.
+ * @param identifier The identifier.
+ * @param revision The revision number or -1 if the latest revision should be used.
+ */
+ protected DocumentImpl(org.apache.lenya.cms.publication.Session session,
+ DocumentIdentifier identifier, int revision) {
+
+ if (logger.isDebugEnabled()) {
+ logger.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.identifier = identifier;
+ this.session = session;
+ this.revision = revision;
+
+ if (logger.isDebugEnabled()) {
+ logger.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) {
+ this.publication = getSession().getPublication(getIdentifier().getPublicationId());
+ }
+ return this.publication;
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.Document#getLastModified()
+ */
+ public long getLastModified() throws DocumentException {
+ try {
+ return getRepositoryNode().getLastModified();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new DocumentException(e);
+ }
+ }
+
+ public String getLanguage() {
+ return this.identifier.getLanguage();
+ }
+
+ public String[] getLanguages() {
+
+ List documentLanguages = new ArrayList();
+ String[] allLanguages = getPublication().getLanguages();
+
+ if (logger.isDebugEnabled()) {
+ logger.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()]);
+ }
+
+ 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("")) {
+ logger.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) {
+ logger.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) {
+ Validate.notNull(_extension);
+ Validate.isTrue(!_extension.startsWith("."), "Extension must start with a dot");
+ checkWritability();
+ this.extension = _extension;
+ }
+
+ public boolean exists() throws ResourceNotFoundException {
+ try {
+ return getRepositoryNode().exists();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new ResourceNotFoundException(e);
+ }
+ }
+
+ public boolean existsInAnyLanguage() throws ResourceNotFoundException {
+ String[] languages = getLanguages();
+
+ if (languages.length > 0) {
+ if (logger.isDebugEnabled()) {
+ logger.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 (logger.isDebugEnabled()) {
+ logger.debug("Document (" + this
+ + ") exists even in all languages of this publication");
+ }
+ return true;
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.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().getPubBaseUri() + ":"
+ + getArea() + ":" + getUUID() + ":" + getLanguage();
+
+ return key.hashCode();
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return getIdentifier().toString();
+ }
+
+ /**
+ * @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(getSession(), getLocator());
+ String prefix = "/" + getPublication().getId() + "/" + getArea();
+ return webappUrl.substring(prefix.length());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public org.apache.lenya.cms.publication.Session getSession() {
+ return this.session;
+ }
+
+ public void accept(DocumentVisitor visitor) throws Exception {
+ 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;
+ private MetaDataCache metaDataCache;
+ private SourceResolver resolver;
+
+ /**
+ * 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) {
+ String name;
+ try {
+ name = getMetaData(METADATA_NAMESPACE).getFirstValue(METADATA_RESOURCE_TYPE);
+ } catch (MetaDataException e) {
+ throw new DocumentException(e);
+ }
+ if (name == null) {
+ throw new DocumentException("No resource type defined for document [" + this + "]!");
+ }
+ this.resourceType = this.resourceTypeResolver.getResourceType(name);
+ }
+ return this.resourceType;
+ }
+
+ public MetaData getMetaData(String namespaceUri) throws MetaDataException {
+ MetaData meta;
+ try {
+ meta = new MetaDataWrapper(getContentHolder().getMetaData(namespaceUri));
+ } catch (org.apache.lenya.cms.repository.metadata.MetaDataException e) {
+ throw new MetaDataException(e);
+ }
+ if (getRepositorySession().isModifiable()) {
+ return meta;
+ } else {
+ String cacheKey = getPublication().getId() + ":" + getArea() + ":" + getUUID() + ":"
+ + getLanguage();
+ return getMetaDataCache().getMetaData(cacheKey, meta, namespaceUri);
+ }
+ }
+
+ protected MetaDataCache getMetaDataCache() {
+ return this.metaDataCache;
+ }
+
+ public String[] getMetaDataNamespaceUris() throws MetaDataException {
+ try {
+ return getContentHolder().getMetaDataNamespaceUris();
+ } catch (org.apache.lenya.cms.repository.metadata.MetaDataException e) {
+ throw new MetaDataException(e);
+ }
+ }
+
+ 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() {
+ try {
+ return getContentHolder().getContentLength();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setMimeType(String mimeType) {
+ checkWritability();
+ try {
+ getMetaData(METADATA_NAMESPACE).setValue(METADATA_MIME_TYPE, mimeType);
+ } catch (MetaDataException e) {
+ throw new RuntimeException(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.resolver);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean existsTranslation(String language) {
+ return area().contains(getUUID(), language);
+ }
+
+ public Document getAreaVersion(String area) throws ResourceNotFoundException {
+ return getPublication().getArea(area).getDocument(getUUID(), getLanguage());
+ }
+
+ public Document getTranslation(String language) throws ResourceNotFoundException {
+ return area().getDocument(getUUID(), language);
+ }
+
+ private Node repositoryNode;
+
+ public Node getRepositoryNode() {
+ if (this.repositoryNode == null) {
+ SessionHolder holder = (SessionHolder) getSession();
+ this.repositoryNode = getRepositoryNode(getNodeFactory(),
+ holder.getRepositorySession(), getSourceURI());
+ }
+ return this.repositoryNode;
+ }
+
+ protected ContentHolder getContentHolder() {
+ Node node = getRepositoryNode();
+ if (isRevisionSpecified()) {
+ try {
+ return node.getHistory().getRevision(revision);
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ return node;
+ }
+ }
+
+ protected static Node getRepositoryNode(NodeFactory nodeFactory, Session session,
+ String sourceUri) {
+ try {
+ return (Node) session.getRepositoryItem(nodeFactory, sourceUri);
+ } catch (Exception e) {
+ throw new RuntimeException("Creating repository node failed: ", e);
+ }
+ }
+
+ /**
+ * @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.getContentUri(area) + "/" + path;
+ }
+
+ public boolean existsVersion(String area, String language) {
+ String sourceUri = getSourceURI(getPublication(), area, getUUID(), language);
+ try {
+ return SourceUtil.exists(sourceUri, getSourceResolver());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Document getVersion(String area, String language) throws ResourceNotFoundException {
+ return getPublication().getArea(area).getDocument(getUUID(), language);
+ }
+
+ 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() {
+ return getPublication().getArea(getArea());
+ }
+
+ public void setResourceType(ResourceType resourceType) {
+ Validate.notNull(resourceType);
+ checkWritability();
+ 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) {
+ Validate.notNull(extension);
+ Validate.isTrue(!extension.startsWith("."), "Extension must start with a dot");
+ checkWritability();
+ 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 (org.apache.lenya.cms.repository.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 (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Session getRepositorySession() {
+ return ((SessionHolder) getSession()).getRepositorySession();
+ }
+
+ public int getRevisionNumber() {
+ if (!isRevisionSpecified()) {
+ throw new UnsupportedOperationException(
+ "This is not a particular revision of the document [" + this + "].");
+ }
+ return this.revision;
+ }
+
+ public void setMetaDataCache(MetaDataCache metaDataCache) {
+ this.metaDataCache = metaDataCache;
+ }
+
+ public SourceResolver getSourceResolver() {
+ return resolver;
+ }
+
+ public void setSourceResolver(SourceResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ public NodeFactory getNodeFactory() {
+ return nodeFactory;
+ }
+
+ public void setNodeFactory(NodeFactory nodeFactory) {
+ this.nodeFactory = nodeFactory;
+ }
+
+ public void checkin() throws RepositoryException {
+ try {
+ getRepositoryNode().checkin();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public boolean isCheckedOutBySession(String sessionId, String userId)
+ throws RepositoryException {
+ try {
+ return getRepositoryNode().isCheckedOutBySession(sessionId, userId);
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public void checkout() throws RepositoryException {
+ try {
+ getRepositoryNode().checkout();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public String getCheckoutUserId() throws RepositoryException {
+ try {
+ return getRepositoryNode().getCheckoutUserId();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public boolean isCheckedOut() throws RepositoryException {
+ try {
+ return getRepositoryNode().isCheckedOut();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public void lock() throws RepositoryException {
+ try {
+ getRepositoryNode().lock();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public void registerDirty() throws RepositoryException {
+ try {
+ getRepositoryNode().registerDirty();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public void unlock() throws RepositoryException {
+ try {
+ getRepositoryNode().unlock();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ private History history;
+
+ public History getHistory() {
+ if (this.history == null) {
+ this.history = new HistoryWrapper(getRepositoryNode().getHistory());
+ }
+ return this.history;
+ }
+
+ public boolean isLocked() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public Document getRevision(int i) {
+ return area().getDocument(getUUID(), getLanguage(), i);
+ }
+
+ public void forceCheckIn() throws RepositoryException {
+ try {
+ getRepositoryNode().forceCheckIn();
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public void rollback(int revision) throws RepositoryException {
+ try {
+ getRepositoryNode().rollback(revision);
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public void checkout(boolean checkoutRestrictedToSession) throws RepositoryException {
+ try {
+ getRepositoryNode().checkout(checkoutRestrictedToSession);
+ } catch (org.apache.lenya.cms.repository.RepositoryException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
+ public void setResourceTypeResolver(ResourceTypeResolver resourceTypeResolver) {
+ this.resourceTypeResolver = resourceTypeResolver;
+ }
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentLocator.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentLocator.java
new file mode 100644
index 0000000..c215ddf
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentLocator.java
@@ -0,0 +1,210 @@
+/*
+ * 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.util.Map;
+import java.util.WeakHashMap;
+
+
+/**
+ * A DocumentLocator describes a document based on its path in the site structure. The actual
+ * document doesn't have to exist.
+ */
+public class DocumentLocator {
+
+ private static Map locators = new WeakHashMap();
+
+ /**
+ * Returns a specific document locator.
+ * @param pubId The publication ID.
+ * @param area The area of the document.
+ * @param path The path of the document in the site structure.
+ * @param language The language of the document.
+ * @return A document locator.
+ */
+ public static DocumentLocator getLocator(String pubId, String area, String path, String language) {
+ String key = DocumentLocator.getKey(pubId, area, path, language);
+ DocumentLocator locator = (DocumentLocator) locators.get(key);
+ if (locator == null) {
+ locator = new DocumentLocator(pubId, area, path, language);
+ locators.put(key, locator);
+ }
+ return locator;
+ }
+
+ protected static final String getKey(String pubId, String area, String path, String language) {
+ return pubId + ":" + area + ":" + path + ":" + language;
+ }
+
+ private String pubId;
+ private String area;
+ private String path;
+ private String language;
+
+ protected DocumentLocator(String pubId, String area, String path, String language) {
+ this.path = path;
+ this.pubId = pubId;
+ this.area = area;
+ this.language = language;
+ }
+
+ /**
+ * @return The area of the document.
+ */
+ public String getArea() {
+ return area;
+ }
+
+ /**
+ * @return The language of the document.
+ */
+ public String getLanguage() {
+ return language;
+ }
+
+ /**
+ * @return The path of the document in the site structure.
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * @return The publication ID.
+ */
+ public String getPublicationId() {
+ return pubId;
+ }
+
+ /**
+ * Returns a locator with the same publication ID, area, and language, but a different path in
+ * the site structure.
+ * @param path The path.
+ * @return A document locator.
+ */
+ public DocumentLocator getPathVersion(String path) {
+ return DocumentLocator.getLocator(getPublicationId(), getArea(), path, getLanguage());
+ }
+
+ /**
+ * Returns a descendant of this locator.
+ * @param relativePath The relative path which must not begin with a slash and must not be
+ * empty.
+ * @return A document locator.
+ */
+ public DocumentLocator getDescendant(String relativePath) {
+ if (relativePath.length() == 0) {
+ throw new IllegalArgumentException("The relative path must not be empty!");
+ }
+ if (relativePath.startsWith("/")) {
+ throw new IllegalArgumentException("The relative path must not start with a slash!");
+ }
+ return getPathVersion(getPath() + "/" + relativePath);
+ }
+
+ /**
+ * Returns a child of this locator.
+ * @param step The relative path to the child, it must not contain a slash.
+ * @return A document locator.
+ */
+ public DocumentLocator getChild(String step) {
+ if (step.indexOf("/") > -1) {
+ throw new IllegalArgumentException("The step [" + step + "] must not contain a slash!");
+ }
+ return getDescendant(step);
+ }
+
+ /**
+ * Returns the parent of this locator.
+ * @return A document locator or <code>null</code> if this is the root locator.
+ */
+ public DocumentLocator getParent() {
+ int lastSlashIndex = getPath().lastIndexOf("/");
+ if (lastSlashIndex > -1) {
+ String parentPath = getPath().substring(0, lastSlashIndex);
+ return getPathVersion(parentPath);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the parent of this locator.
+ * @param defaultPath The path of the locator to return if this is the root locator.
+ * @return A document locator.
+ */
+ public DocumentLocator getParent(String defaultPath) {
+ DocumentLocator parent = getParent();
+ if (parent != null) {
+ return parent;
+ } else {
+ return getPathVersion(defaultPath);
+ }
+ }
+
+ /**
+ * Returns a locator with the same publication ID, area, and path, but with a different
+ * language.
+ * @param language The language.
+ * @return A document locator.
+ */
+ public DocumentLocator getLanguageVersion(String language) {
+ return DocumentLocator.getLocator(getPublicationId(), getArea(), getPath(), language);
+ }
+
+ protected String getKey() {
+ return DocumentLocator.getKey(getPublicationId(), getArea(), getPath(), getLanguage());
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DocumentLocator)) {
+ return false;
+ }
+ DocumentLocator locator = (DocumentLocator) obj;
+ return locator.getKey().equals(getKey());
+ }
+
+ public int hashCode() {
+ return getKey().hashCode();
+ }
+
+ public String toString() {
+ return getKey();
+ }
+
+ /**
+ * Returns a locator with the same publication ID, path, and language, but with a different
+ * area.
+ * @param area The area.
+ * @return A document locator.
+ */
+ public DocumentLocator getAreaVersion(String area) {
+ return DocumentLocator.getLocator(getPublicationId(), area, getPath(), getLanguage());
+ }
+
+ public Document getDocument(Session session) throws ResourceNotFoundException {
+ try {
+ Publication pub = session.getPublication(getPublicationId());
+ return pub.getArea(getArea()).getSite().getNode(getPath()).getLink(getLanguage())
+ .getDocument();
+ } catch (PublicationException e) {
+ throw new ResourceNotFoundException(e);
+ }
+ }
+
+}
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentManager.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentManager.java
new file mode 100644
index 0000000..77fab08
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentManager.java
@@ -0,0 +1,252 @@
+/*
+ * 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 org.apache.lenya.cms.publication.util.DocumentSet;
+
+/**
+ * Helper to manage documents. It takes care of attachments etc.
+ *
+ * @version $Id$
+ */
+public interface DocumentManager {
+
+ /**
+ * The Avalon component role.
+ */
+ String ROLE = DocumentManager.class.getName();
+
+ /**
+ * Copies a document from one location to another location.
+ * @param sourceDocument The document to copy.
+ * @param destination The destination document.
+ * @throws PublicationException if a document which destinationDocument depends on does not
+ * exist.
+ */
+ void copy(Document sourceDocument, DocumentLocator destination) throws PublicationException;
+
+ /**
+ * Copies a document to another area.
+ * @param sourceDocument The document to copy.
+ * @param destinationArea The destination area.
+ * @throws PublicationException if a document which the destination document depends on does not
+ * exist.
+ */
+ void copyToArea(Document sourceDocument, String destinationArea) throws PublicationException;
+
+ /**
+ * Copies a document set to another area.
+ * @param documentSet The document set to copy.
+ * @param destinationArea The destination area.
+ * @throws PublicationException if a document which one of the destination documents depends on
+ * does not exist.
+ */
+ void copyToArea(DocumentSet documentSet, String destinationArea) throws PublicationException;
+
+ /**
+ * Creates a new document in the same publication the <code>parentDocument</code> belongs to
+ * with the given parameters:
+ *
+ * @param sourceDocument The document to initialize the contents and meta data from.
+ * @param area The target area.
+ * @param path The target path.
+ * @param language The target language.
+ * @param extension The extension to use for the document source.
+ * @param navigationTitle navigation title
+ * @param visibleInNav determines the visibility of a node in the navigation
+ * @return The added document.
+ *
+ * @throws DocumentBuildException if the document can not be created
+ * @throws PublicationException if the document is already contained.
+ */
+ Document add(Document sourceDocument, String area, String path, String language,
+ String extension, String navigationTitle, boolean visibleInNav)
+ throws DocumentBuildException, PublicationException;
+
+ /**
+ * Creates a new document with the given parameters:
+ * @param resourceType the document type (aka resource type) of the new document
+ * @param contentSourceUri The URI to read the content from.
+ * @param pub The publication.
+ * @param area The area.
+ * @param path The path.
+ * @param language The language.
+ * @param extension The extension to use for the document source, without the leading dot.
+ * @param navigationTitle The navigation title.
+ * @param visibleInNav The navigation visibility.
+ * @return The added document.
+ *
+ * @throws DocumentBuildException if the document can not be created
+ * @throws PublicationException if the document is already contained.
+ */
+ Document add(ResourceType resourceType, String contentSourceUri,
+ Publication pub, String area, String path, String language, String extension,
+ String navigationTitle, boolean visibleInNav) throws DocumentBuildException,
+ PublicationException;
+
+ /**
+ * Creates a new document without adding it to the site structure.
+ * @param resourceType the document type (aka resource type) of the new document
+ * @param contentSourceUri The URI to read the content from.
+ * @param pub The publication.
+ * @param area The area.
+ * @param language The language.
+ * @param extension The extension to use for the document source, without the leading dot.
+ * @return The added document.
+ *
+ * @throws DocumentBuildException if the document can not be created
+ * @throws PublicationException if the document is already contained.
+ */
+ Document add(ResourceType resourceType, String contentSourceUri,
+ Publication pub, String area, String language, String extension)
+ throws DocumentBuildException, PublicationException;
+
+ /**
+ * Adds a new version of a document with a different language and / or in a different area.
+ *
+ * @param sourceDocument The document to initialize the contents and meta data from.
+ * @param area The area.
+ * @param language The language of the new document.
+ * @return The added document.
+ *
+ * @throws DocumentBuildException if the document can not be created
+ * @throws PublicationException if the document is already contained.
+ */
+ Document addVersion(Document sourceDocument, String area, String language)
+ throws DocumentBuildException, PublicationException;
+
+ /**
+ * Adds a new version of a document with a different language and / or in a different area.
+ *
+ * @param sourceDocument The document to initialize the contents and meta data from.
+ * @param area The area.
+ * @param language The language of the new document.
+ * @param addToSite If the new version should be added to the site structure.
+ * @return The added document.
+ *
+ * @throws DocumentBuildException if the document can not be created
+ * @throws PublicationException if the document is already contained.
+ */
+ Document addVersion(Document sourceDocument, String area, String language, boolean addToSite)
+ throws DocumentBuildException, PublicationException;
+
+ /**
+ * Deletes a document from the content repository and from the site structure.
+ * @param document The document to delete.
+ * @throws PublicationException when something went wrong.
+ */
+ void delete(Document document) throws PublicationException;
+
+ /**
+ * Moves a document from one location to another.
+ * @param sourceDocument The source document.
+ * @param destination The destination document.
+ * @throws PublicationException if a document which the destination document depends on does not
+ * exist.
+ */
+ void move(Document sourceDocument, DocumentLocator destination) throws PublicationException;
+
+ /**
+ * Moves a document set from one location to another. A source is moved to the destination of
+ * the same position in the set.
+ * @param sources The source documents.
+ * @param destinations The destination documents.
+ * @throws PublicationException if a document which the destination document depends on does not
+ * exist.
+ */
+ void move(DocumentSet sources, DocumentSet destinations) throws PublicationException;
+
+ /**
+ * Copies a document set from one location to another. A source is copied to the destination of
+ * the same position in the set.
+ * @param sources The source documents.
+ * @param destinations The destination documents.
+ * @throws PublicationException if a document which the destination document depends on does not
+ * exist.
+ */
+ void copy(DocumentSet sources, DocumentSet destinations) throws PublicationException;
+
+ /**
+ * Moves a document to another location, incl. all requiring documents. If a sitetree is used,
+ * this means that the whole subtree is moved.
+ * @param sourceArea The source area.
+ * @param sourcePath The source path.
+ * @param targetArea The target area.
+ * @param targetPath The target path.
+ * @throws PublicationException if an error occurs.
+ */
+ void moveAll(Area sourceArea, String sourcePath, Area targetArea, String targetPath)
+ throws PublicationException;
+
+ /**
+ * Moves all language versions of a document to another location.
+ * @param sourceArea The source area.
+ * @param sourcePath The source path.
+ * @param targetArea The target area.
+ * @param targetPath The target path.
+ * @throws PublicationException if the documents could not be moved.
+ */
+ void moveAllLanguageVersions(Area sourceArea, String sourcePath, Area targetArea,
+ String targetPath) throws PublicationException;
+
+ /**
+ * Copies a document to another location, incl. all requiring documents. If a sitetree is used,
+ * this means that the whole subtree is copied.
+ * @param sourceArea The source area.
+ * @param sourcePath The source path.
+ * @param targetArea The target area.
+ * @param targetPath The target path.
+ * @throws PublicationException if an error occurs.
+ */
+ void copyAll(Area sourceArea, String sourcePath, Area targetArea, String targetPath)
+ throws PublicationException;
+
+ /**
+ * Copies all language versions of a document to another location.
+ * @param sourceArea The source area.
+ * @param sourcePath The source path.
+ * @param targetArea The target area.
+ * @param targetPath The target path.
+ * @throws PublicationException if the documents could not be copied.
+ */
+ void copyAllLanguageVersions(Area sourceArea, String sourcePath, Area targetArea,
+ String targetPath) throws PublicationException;
+
+ /**
+ * Deletes a document, incl. all requiring documents. If a sitetree is used, this means that the
+ * whole subtree is deleted.
+ * @param document The document.
+ * @throws PublicationException if an error occurs.
+ */
+ void deleteAll(Document document) throws PublicationException;
+
+ /**
+ * Deletes all language versions of a document.
+ * @param document The document.
+ * @throws PublicationException if the documents could not be copied.
+ */
+ void deleteAllLanguageVersions(Document document) throws PublicationException;
+
+ /**
+ * Deletes a set of documents.
+ * @param documents The documents.
+ * @throws PublicationException if an error occurs.
+ */
+ void delete(DocumentSet documents) throws PublicationException;
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentManagerImpl.java b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentManagerImpl.java
new file mode 100644
index 0000000..f49d155
--- /dev/null
+++ b/org.apache.lenya.core.document/src/main/java/org/apache/lenya/cms/publication/DocumentManagerImpl.java
@@ -0,0 +1,862 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cocoon.spring.configurator.WebAppContextUtils;
+import org.apache.cocoon.util.AbstractLogEnabled;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.excalibur.source.Source;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.lenya.cms.metadata.MetaData;
+import org.apache.lenya.cms.metadata.MetaDataException;
+import org.apache.lenya.cms.publication.util.DocumentSet;
+import org.apache.lenya.cms.publication.util.DocumentVisitor;
+import org.apache.lenya.cms.repository.Node;
+import org.apache.lenya.cms.repository.NodeFactory;
+import org.apache.lenya.cms.repository.UUIDGenerator;
+import org.apache.lenya.cms.site.Link;
+import org.apache.lenya.cms.site.NodeIterator;
+import org.apache.lenya.cms.site.NodeSet;
+import org.apache.lenya.cms.site.SiteException;
+import org.apache.lenya.cms.site.SiteManager;
+import org.apache.lenya.cms.site.SiteNode;
+import org.apache.lenya.cms.site.SiteStructure;
+import org.apache.lenya.cms.site.SiteUtil;
+
+/**
+ * DocumentManager implementation.
+ *
+ * @version $Id$
+ */
+public class DocumentManagerImpl extends AbstractLogEnabled implements DocumentManager {
+
+ private SourceResolver sourceResolver;
+ private UUIDGenerator uuidGenerator;
+ private NodeFactory nodeFactory;
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#add(org.apache.lenya.cms.publication.Document,
+ * java.lang.String, java.lang.String, java.lang.String, java.lang.String,
+ * java.lang.String, boolean)
+ */
+ public Document add(Document sourceDocument, String area, String path, String language,
+ String extension, String navigationTitle, boolean visibleInNav)
+ throws DocumentBuildException, PublicationException {
+
+ Document document = add(sourceDocument.getResourceType(), sourceDocument.getInputStream(),
+ sourceDocument.getPublication(), area, path, language, extension, navigationTitle,
+ visibleInNav, sourceDocument.getMimeType());
+
+ copyMetaData(sourceDocument, document);
+ return document;
+ }
+
+ /**
+ * Copies meta data from one document to another. If the destination document is a different
+ * area version, the meta data are duplicated (i.e., onCopy = delete is neglected).
+ * @param source
+ * @param destination
+ * @throws PublicationException
+ */
+ protected void copyMetaData(Document source, Document destination) throws PublicationException {
+
+ boolean duplicate = source.getUUID().equals(destination.getUUID())
+ && source.getLanguage().equals(destination.getLanguage())
+ && !source.getArea().equals(destination.getArea());
+
+ try {
+ String[] uris = source.getMetaDataNamespaceUris();
+ for (int i = 0; i < uris.length; i++) {
+ if (duplicate) {
+ destination.getMetaData(uris[i]).forcedReplaceBy(source.getMetaData(uris[i]));
+ } else {
+ destination.getMetaData(uris[i]).replaceBy(source.getMetaData(uris[i]));
+ }
+ }
+ } catch (MetaDataException e) {
+ throw new PublicationException(e);
+ }
+ }
+
+ public Document add(ResourceType documentType, String initialContentsURI, Publication pub,
+ String area, String path, String language, String extension, String navigationTitle,
+ boolean visibleInNav) throws DocumentBuildException, DocumentException,
+ PublicationException {
+
+ Area areaObj = pub.getArea(area);
+ SiteStructure site = areaObj.getSite();
+ if (site.contains(path) && site.getNode(path).hasLink(language)) {
+ throw new DocumentException("The link [" + path + ":" + language
+ + "] is already contained in site [" + site + "]");
+ }
+
+ Document document = add(documentType, initialContentsURI, pub, area, language, extension);
+
+ addToSiteManager(path, document, navigationTitle, visibleInNav);
+ return document;
+ }
+
+ protected Document add(ResourceType documentType, InputStream initialContentsStream,
+ Publication pub, String area, String path, String language, String extension,
+ String navigationTitle, boolean visibleInNav, String mimeType)
+ throws DocumentBuildException, DocumentException, PublicationException {
+
+ Area areaObj = pub.getArea(area);
+ SiteStructure site = areaObj.getSite();
+ if (site.contains(path) && site.getNode(path).hasLink(language)) {
+ throw new DocumentException("The link [" + path + ":" + language
+ + "] is already contained in site [" + site + "]");
+ }
+
+ Document document = add(documentType, initialContentsStream, pub, area, language,
+ extension, mimeType);
+
+ addToSiteManager(path, document, navigationTitle, visibleInNav);
+ return document;
+ }
+
+ public Document add(ResourceType documentType, String initialContentsURI, Publication pub,
+ String area, String language, String extension) throws DocumentBuildException,
+ DocumentException, PublicationException {
+
+ String uuid = getUuidGenerator().nextUUID();
+ Source source = null;
+ try {
+ source = getSourceResolver().resolveURI(initialContentsURI);
+ return add(documentType, uuid, source.getInputStream(), pub, area, language, extension,
+ getMimeType(source));
+ } catch (Exception e) {
+ throw new PublicationException(e);
+ } finally {
+ if (source != null) {
+ getSourceResolver().release(source);
+ }
+ }
+ }
+
+ protected String getMimeType(Source source) {
+ String mimeType = source.getMimeType();
+ if (mimeType == null) {
+ mimeType = "";
+ }
+ return mimeType;
+ }
+
+ protected Document add(ResourceType documentType, InputStream initialContentsStream,
+ Publication pub, String area, String language, String extension, String mimeType)
+ throws DocumentBuildException, DocumentException, PublicationException {
+
+ String uuid = getUuidGenerator().nextUUID();
+ return add(documentType, uuid, initialContentsStream, pub, area, language, extension,
+ mimeType);
+ }
+
+ protected Document add(ResourceType documentType, String uuid, InputStream stream,
+ Publication pub, String area, String language, String extension, String mimeType)
+ throws DocumentBuildException {
+ try {
+
+ Area areaObj = pub.getArea(area);
+ if (areaObj.contains(uuid, language)) {
+ throw new DocumentBuildException("The document [" + pub.getId() + ":" + area + ":"
+ + uuid + ":" + language + "] already exists!");
+ }
+
+ Document document = areaObj.getDocument(uuid, language);
+ document.lock();
+
+ document.setResourceType(documentType);
+ document.setSourceExtension(extension);
+ document.setMimeType(mimeType);
+
+ // Write Lenya-internal meta-data
+ MetaData lenyaMetaData = document.getMetaData(DocumentImpl.METADATA_NAMESPACE);
+ lenyaMetaData.setValue(DocumentImpl.METADATA_CONTENT_TYPE, "xml");
+
+ if (getLogger().isDebugEnabled()) {
+ getLogger().debug("Create");
+ getLogger().debug(" document: [" + document + "]");
+ }
+
+ create(stream, document);
+ return document;
+ } catch (Exception e) {
+ throw new DocumentBuildException("call to creator for new document failed", e);
+ }
+ }
+
+ protected void create(InputStream stream, Document document) throws Exception {
+
+ // Read initial contents as DOM
+ if (getLogger().isDebugEnabled())
+ getLogger().debug(
+ "DefaultCreator::create(), ready to read initial contents from URI [" + stream
+ + "]");
+
+ copy(getSourceResolver(), stream, document);
+ }
+
+ protected void copy(SourceResolver resolver, InputStream sourceInputStream, Document destination)
+ throws IOException {
+
+ boolean useBuffer = true;
+
+ OutputStream destOutputStream = null;
+ try {
+ destOutputStream = destination.getOutputStream();
+
+ if (useBuffer) {
+ final ByteArrayOutputStream sourceBos = new ByteArrayOutputStream();
+ IOUtils.copy(sourceInputStream, sourceBos);
+ IOUtils.write(sourceBos.toByteArray(), destOutputStream);
+ } else {
+ IOUtils.copy(sourceInputStream, destOutputStream);
+ }
+ } finally {
+ if (destOutputStream != null) {
+ destOutputStream.flush();
+ destOutputStream.close();
+ }
+ if (sourceInputStream != null) {
+ sourceInputStream.close();
+ }
+ }
+ }
+
+ protected void addToSiteManager(String path, Document document, String navigationTitle,
+ boolean visibleInNav) throws PublicationException {
+ addToSiteManager(path, document, navigationTitle, visibleInNav, null);
+ }
+
+ protected void addToSiteManager(String path, Document document, String navigationTitle,
+ boolean visibleInNav, String followingSiblingPath) throws PublicationException {
+ SiteStructure site = document.area().getSite();
+ if (!site.contains(path) && followingSiblingPath != null) {
+ site.add(path, followingSiblingPath);
+ }
+ site.add(path, document);
+ document.getLink().setLabel(navigationTitle);
+ document.getLink().getNode().setVisible(visibleInNav);
+ }
+
+ /**
+ * Template method to copy a document. Override {@link #copyDocumentSource(Document, Document)}
+ * to implement access to a custom repository.
+ * @see org.apache.lenya.cms.publication.DocumentManager#copy(org.apache.lenya.cms.publication.Document,
+ * org.apache.lenya.cms.publication.DocumentLocator)
+ */
+ public void copy(Document sourceDoc, DocumentLocator destination) throws PublicationException {
+
+ if (!destination.getPublicationId().equals(sourceDoc.getPublication().getId())) {
+ throw new PublicationException("Can't copy to a different publication!");
+ }
+
+ SiteStructure destSite = sourceDoc.getPublication().getArea(destination.getArea())
+ .getSite();
+ String destPath = destination.getPath();
+ if (destSite.contains(destination.getPath(), destination.getLanguage())) {
+ Document destDoc = destSite.getNode(destPath).getLink(destination.getLanguage())
+ .getDocument();
+ copyDocumentSource(sourceDoc, destDoc);
+ copyInSiteStructure(sourceDoc, destDoc, destPath);
+ } else {
+ add(sourceDoc, destination.getArea(), destPath, destination.getLanguage(), sourceDoc
+ .getExtension(), sourceDoc.getLink().getLabel(), sourceDoc.getLink().getNode()
+ .isVisible());
+ }
+
+ }
+
+ protected void copyInSiteStructure(Document sourceDoc, Document destDoc, String destPath)
+ throws PublicationException, DocumentException, SiteException {
+
+ String destArea = destDoc.getArea();
+
+ SiteStructure destSite = sourceDoc.getPublication().getArea(destArea).getSite();
+
+ if (sourceDoc.hasLink()) {
+ if (destDoc.hasLink()) {
+ Link srcLink = sourceDoc.getLink();
+ Link destLink = destDoc.getLink();
+ destLink.setLabel(srcLink.getLabel());
+ destLink.getNode().setVisible(srcLink.getNode().isVisible());
+ } else {
+ String label = sourceDoc.getLink().getLabel();
+ boolean visible = sourceDoc.getLink().getNode().isVisible();
+ if (destSite.contains(sourceDoc.getLink().getNode().getPath())) {
+ addToSiteManager(destPath, destDoc, label, visible);
+ } else {
+
+ String followingSiblingPath = null;
+
+ if (sourceDoc.getPath().equals(destPath)) {
+ SiteStructure sourceSite = sourceDoc.area().getSite();
+
+ SiteNode[] sourceSiblings;
+ SiteNode sourceNode = sourceDoc.getLink().getNode();
+ if (sourceNode.isTopLevel()) {
+ sourceSiblings = sourceSite.getTopLevelNodes();
+ } else if (sourceNode.getParent() != null) {
+ sourceSiblings = sourceNode.getParent().getChildren();
+ } else {
+ sourceSiblings = new SiteNode[1];
+ sourceSiblings[0] = sourceNode;
+ }
+
+ final int sourcePos = Arrays.asList(sourceSiblings).indexOf(sourceNode);
+
+ int pos = sourcePos;
+ while (followingSiblingPath == null && pos < sourceSiblings.length) {
+ String siblingPath = sourceSiblings[pos].getPath();
+ if (destSite.contains(siblingPath)) {
+ followingSiblingPath = siblingPath;
+ }
+ pos++;
+ }
+ }
+
+ if (followingSiblingPath == null) {
+ addToSiteManager(destPath, destDoc, label, visible);
+ } else {
+ addToSiteManager(destPath, destDoc, label, visible, followingSiblingPath);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#delete(org.apache.lenya.cms.publication.Document)
+ */
+ public void delete(Document document) throws PublicationException {
+ if (!document.exists()) {
+ throw new PublicationException("Document [" + document + "] does not exist!");
+ }
+
+ if (document.hasLink()) {
+ document.getLink().delete();
+ }
+
+ document.delete();
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#move(org.apache.lenya.cms.publication.Document,
+ * org.apache.lenya.cms.publication.DocumentLocator)
+ */
+ public void move(Document sourceDocument, DocumentLocator destination)
+ throws PublicationException {
+
+ if (!destination.getArea().equals(sourceDocument.getArea())) {
+ throw new PublicationException("Can't move to a different area!");
+ }
+
+ SiteStructure site = sourceDocument.area().getSite();
+ if (site.contains(destination.getPath())) {
+ throw new PublicationException("The path [" + destination
+ + "] is already contained in this publication!");
+ }
+
+ String label = sourceDocument.getLink().getLabel();
+ boolean visible = sourceDocument.getLink().getNode().isVisible();
+ sourceDocument.getLink().delete();
+
+ site.add(destination.getPath(), sourceDocument);
+ sourceDocument.getLink().setLabel(label);
+ sourceDocument.getLink().getNode().setVisible(visible);
+
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#copyToArea(org.apache.lenya.cms.publication.Document,
+ * java.lang.String)
+ */
+ public void copyToArea(Document sourceDoc, String destinationArea) throws PublicationException {
+ String language = sourceDoc.getLanguage();
+ copyToVersion(sourceDoc, destinationArea, language);
+ }
+
+ protected void copyToVersion(Document sourceDoc, String destinationArea, String language)
+ throws DocumentException, DocumentBuildException, PublicationException, SiteException {
+
+ Document destDoc;
+ if (sourceDoc.existsAreaVersion(destinationArea)) {
+ destDoc = sourceDoc.getAreaVersion(destinationArea);
+ copyDocumentSource(sourceDoc, destDoc);
+ } else {
+ destDoc = addVersion(sourceDoc, destinationArea, language);
+ }
+
+ if (sourceDoc.hasLink()) {
+ copyInSiteStructure(sourceDoc, destDoc, sourceDoc.getPath());
+ }
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#copyToArea(org.apache.lenya.cms.publication.util.DocumentSet,
+ * java.lang.String)
+ */
+ public void copyToArea(DocumentSet documentSet, String destinationArea)
+ throws PublicationException {
+ Document[] documents = documentSet.getDocuments();
+ for (int i = 0; i < documents.length; i++) {
+ copyToArea(documents[i], destinationArea);
+ }
+ }
+
+ public void moveAll(Area sourceArea, String sourcePath, Area targetArea, String targetPath)
+ throws PublicationException {
+ SiteStructure site = sourceArea.getSite();
+
+ SiteNode root = site.getNode(sourcePath);
+ List subsite = preOrder(root);
+
+ for (Iterator n = subsite.iterator(); n.hasNext();) {
+ SiteNode node = (SiteNode) n.next();
+ String subPath = node.getPath().substring(sourcePath.length());
+ targetArea.getSite().add(targetPath + subPath);
+ }
+ Collections.reverse(subsite);
+ for (Iterator n = subsite.iterator(); n.hasNext();) {
+ SiteNode node = (SiteNode) n.next();
+ String subPath = node.getPath().substring(sourcePath.length());
+ moveAllLanguageVersions(sourceArea, sourcePath + subPath, targetArea, targetPath
+ + subPath);
+ }
+ }
+
+ protected List preOrder(SiteNode node) {
+ List list = new ArrayList();
+ list.add(node);
+ SiteNode[] children = node.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ list.addAll(preOrder(children[i]));
+ }
+ return list;
+ }
+
+ public void moveAllLanguageVersions(Area sourceArea, String sourcePath, Area targetArea,
+ String targetPath) throws PublicationException {
+
+ SiteNode sourceNode = sourceArea.getSite().getNode(sourcePath);
+ String[] languages = sourceNode.getLanguages();
+ for (int i = 0; i < languages.length; i++) {
+ Link sourceLink = sourceNode.getLink(languages[i]);
+ String label = sourceLink.getLabel();
+ Document sourceDoc = sourceLink.getDocument();
+ sourceLink.delete();
+
+ Document targetDoc;
+ if (sourceArea.getName().equals(targetArea.getName())) {
+ targetDoc = sourceDoc;
+ } else {
+ targetDoc = addVersion(sourceDoc, targetArea.getName(), sourceDoc.getLanguage());
+ copyRevisions(sourceDoc, targetDoc);
+ sourceDoc.delete();
+ }
+
+ Link link = targetArea.getSite().add(targetPath, targetDoc);
+ link.setLabel(label);
+ assert targetDoc.getLink().getLabel().equals(label);
+ }
+ SiteNode targetNode = targetArea.getSite().getNode(targetPath);
+ targetNode.setVisible(sourceNode.isVisible());
+ }
+
+ protected void copyRevisions(Document sourceDoc, Document targetDoc)
+ throws PublicationException {
+ try {
+ Node targetNode = ((DocumentImpl) targetDoc).getRepositoryNode();
+ targetNode.copyRevisionsFrom(((DocumentImpl) sourceDoc).getRepositoryNode());
+ } catch (Exception e) {
+ throw new PublicationException(e);
+ }
+ }
+
+ public void copyAll(Area sourceArea, String sourcePath, Area targetArea, String targetPath)
+ throws PublicationException {
+
+ SiteStructure site = sourceArea.getSite();
+ SiteNode root = site.getNode(sourcePath);
+
+ List preOrder = preOrder(root);
+ for (Iterator i = preOrder.iterator(); i.hasNext();) {
+ SiteNode node = (SiteNode) i.next();
+ String nodeSourcePath = node.getPath();
+ String nodeTargetPath = targetPath + nodeSourcePath.substring(sourcePath.length());
+ copyAllLanguageVersions(sourceArea, nodeSourcePath, targetArea, nodeTargetPath);
+ }
+ }
+
+ public void copyAllLanguageVersions(Area sourceArea, String sourcePath, Area targetArea,
+ String targetPath) throws PublicationException {
+ Publication pub = sourceArea.getPublication();
+
+ SiteNode sourceNode = sourceArea.getSite().getNode(sourcePath);
+ String[] languages = sourceNode.getLanguages();
+
+ Document targetDoc = null;
+
+ for (int i = 0; i < languages.length; i++) {
+ Document sourceVersion = sourceNode.getLink(languages[i]).getDocument();
+ DocumentLocator targetLocator = DocumentLocator.getLocator(pub.getId(), targetArea
+ .getName(), targetPath, languages[i]);
+ if (targetDoc == null) {
+ copy(sourceVersion, targetLocator.getLanguageVersion(languages[i]));
+ targetDoc = targetArea.getSite().getNode(targetPath).getLink(languages[i])
+ .getDocument();
+ } else {
+ targetDoc = addVersion(targetDoc, targetLocator.getArea(), languages[i]);
+ addToSiteManager(targetLocator.getPath(), targetDoc, sourceVersion.getLink()
+ .getLabel(), sourceVersion.getLink().getNode().isVisible());
+ copyDocumentSource(sourceVersion, targetDoc);
+ }
+ }
+ }
+
+ /**
+ * Copies a document source.
+ * @param sourceDocument The source document.
+ * @param destinationDocument The destination document.
+ * @throws PublicationException when something went wrong.
+ */
+ public void copyDocumentSource(Document sourceDocument, Document destinationDocument)
+ throws PublicationException {
+ copyContent(sourceDocument, destinationDocument);
+ copyMetaData(sourceDocument, destinationDocument);
+ }
+
+ protected void copyContent(Document sourceDocument, Document destinationDocument)
+ throws PublicationException {
+ boolean useBuffer = true;
+
+ OutputStream destOutputStream = null;
+ InputStream sourceInputStream = null;
+ try {
+ try {
+ sourceInputStream = sourceDocument.getInputStream();
+ destOutputStream = destinationDocument.getOutputStream();
+
+ if (useBuffer) {
+ final ByteArrayOutputStream sourceBos = new ByteArrayOutputStream();
+ IOUtils.copy(sourceInputStream, sourceBos);
+ IOUtils.write(sourceBos.toByteArray(), destOutputStream);
+ } else {
+ IOUtils.copy(sourceInputStream, destOutputStream);
+ }
+ } finally {
+ if (destOutputStream != null) {
+ destOutputStream.flush();
+ destOutputStream.close();
+ }
+ if (sourceInputStream != null) {
+ sourceInputStream.close();
+ }
+ }
+ } catch (Exception e) {
+ throw new PublicationException(e);
+ }
+ }
+
+ /**
+ * Abstract base class for document visitors which operate on a source and target document.
+ */
+ public static abstract class SourceTargetVisitor implements DocumentVisitor {
+
+ private DocumentLocator rootSource;
+ private DocumentLocator rootTarget;
+ private DocumentManager manager;
+
+ /**
+ * Ctor.
+ * @param manager The document manager.
+ * @param source The root source.
+ * @param target The root target.
+ */
+ public SourceTargetVisitor(DocumentManager manager, Document source, DocumentLocator target) {
+ this.manager = manager;
+ this.rootSource = source.getLocator();
+ this.rootTarget = target;
+ }
+
+ /**
+ * @return the root source
+ */
+ protected DocumentLocator getRootSource() {
+ return rootSource;
+ }
+
+ /**
+ * @return the root target
+ */
+ protected DocumentLocator getRootTarget() {
+ return rootTarget;
+ }
+
+ /**
+ * @return the document manager
+ */
+ protected DocumentManager getDocumentManager() {
+ return this.manager;
+ }
+
+ /**
+ * Returns the target corresponding to a source relatively to the root target document.
+ * @param source The source.
+ * @return A document.
+ * @throws DocumentBuildException if the target could not be built.
+ */
+ protected DocumentLocator getTarget(Document source) throws DocumentBuildException {
+ DocumentLocator sourceLocator = source.getLocator();
+ String rootSourcePath = getRootSource().getPath();
+ if (sourceLocator.getPath().equals(rootSourcePath)) {
+ return rootTarget;
+ } else {
+ String relativePath = sourceLocator.getPath().substring(rootSourcePath.length());
+ return rootTarget.getDescendant(relativePath);
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#deleteAll(org.apache.lenya.cms.publication.Document)
+ */
+ public void deleteAll(Document document) throws PublicationException {
+ NodeSet subsite = SiteUtil.getSubSite(document.getLink().getNode());
+ for (NodeIterator i = subsite.descending(); i.hasNext();) {
+ SiteNode node = i.next();
+ String[] languages = node.getLanguages();
+ for (int l = 0; l < languages.length; l++) {
+ Document doc = node.getLink(languages[l]).getDocument();
+ delete(doc);
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#deleteAllLanguageVersions(org.apache.lenya.cms.publication.Document)
+ */
+ public void deleteAllLanguageVersions(Document document) throws PublicationException {
+ String[] languages = document.getLanguages();
+ for (int i = 0; i < languages.length; i++) {
+ delete(document.getTranslation(languages[i]));
+ }
+ }
+
+ /**
+ * Visitor to delete documents.
+ */
+ public static class DeleteVisitor implements DocumentVisitor {
+
+ private DocumentManager manager;
+
+ /**
+ * Ctor.
+ * @param manager The document manager.
+ */
+ public DeleteVisitor(DocumentManager manager) {
+ this.manager = manager;
+ }
+
+ protected DocumentManager getDocumentManager() {
+ return this.manager;
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.util.DocumentVisitor#visitDocument(org.apache.lenya.cms.publication.Document)
+ */
+ public void visitDocument(Document document) throws PublicationException {
+ getDocumentManager().deleteAllLanguageVersions(document);
+ }
+
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#delete(org.apache.lenya.cms.publication.util.DocumentSet)
+ */
+ public void delete(DocumentSet documents) throws PublicationException {
+
+ if (documents.isEmpty()) {
+ return;
+ }
+
+ DocumentSet set = new DocumentSet(documents.getDocuments());
+ sortAscending(set);
+ set.reverse();
+
+ DocumentVisitor visitor = new DeleteVisitor(this);
+ try {
+ set.visit(visitor);
+ } catch (Exception e) {
+ throw new PublicationException(e);
+ }
+
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#move(org.apache.lenya.cms.publication.util.DocumentSet,
+ * org.apache.lenya.cms.publication.util.DocumentSet)
+ */
+ public void move(DocumentSet sources, DocumentSet destinations) throws PublicationException {
+ copy(sources, destinations);
+ delete(sources);
+ /*
+ * Document[] sourceDocs = sources.getDocuments(); Document[] targetDocs =
+ * destinations.getDocuments();
+ *
+ * if (sourceDocs.length != targetDocs.length) { throw new PublicationException( "The number
+ * of source and destination documents must be equal!"); }
+ *
+ * Map source2target = new HashMap(); for (int i = 0; i < sourceDocs.length; i++) {
+ * source2target.put(sourceDocs[i], targetDocs[i]); }
+ *
+ * DocumentSet sortedSources = new DocumentSet(sourceDocs);
+ * SiteUtil.sortAscending(this.manager, sortedSources); Document[] sortedSourceDocs =
+ * sortedSources.getDocuments();
+ *
+ * for (int i = 0; i < sortedSourceDocs.length; i++) { move(sortedSourceDocs[i], (Document)
+ * source2target.get(sortedSourceDocs[i])); }
+ */
+ }
+
+ /**
+ * @see org.apache.lenya.cms.publication.DocumentManager#copy(org.apache.lenya.cms.publication.util.DocumentSet,
+ * org.apache.lenya.cms.publication.util.DocumentSet)
+ */
+ public void copy(DocumentSet sources, DocumentSet destinations) throws PublicationException {
+ Document[] sourceDocs = sources.getDocuments();
+ Document[] targetDocs = destinations.getDocuments();
+
+ if (sourceDocs.length != targetDocs.length) {
+ throw new PublicationException(
+ "The number of source and destination documents must be equal!");
+ }
+
+ Map source2target = new HashMap();
+ for (int i = 0; i < sourceDocs.length; i++) {
+ source2target.put(sourceDocs[i], targetDocs[i]);
+ }
+
+ DocumentSet sortedSources = new DocumentSet(sourceDocs);
+ sortAscending(sortedSources);
+ Document[] sortedSourceDocs = sortedSources.getDocuments();
+
+ for (int i = 0; i < sortedSourceDocs.length; i++) {
+ copy(sortedSourceDocs[i], ((Document) source2target.get(sortedSourceDocs[i]))
+ .getLocator());
+ }
+ }
+
+ protected void sortAscending(DocumentSet set) throws PublicationException {
+
+ if (!set.isEmpty()) {
+
+ Document[] docs = set.getDocuments();
+ int n = docs.length;
+
+ Publication pub = docs[0].getPublication();
+ SiteManager siteManager = (SiteManager) WebAppContextUtils
+ .getCurrentWebApplicationContext().getBean(
+ SiteManager.class.getName() + "/" + pub.getSiteManagerHint());
+
+ Set nodes = new HashSet();
+ for (int i = 0; i < docs.length; i++) {
+ nodes.add(docs[i].getLink().getNode());
+ }
+
+ SiteNode[] ascending = siteManager.sortAscending((SiteNode[]) nodes
+ .toArray(new SiteNode[nodes.size()]));
+
+ set.clear();
+ for (int i = 0; i < ascending.length; i++) {
+ for (int d = 0; d < docs.length; d++) {
+ if (docs[d].getPath().equals(ascending[i].getPath())) {
+ set.add(docs[d]);
+ }
+ }
+ }
+
+ if (set.getDocuments().length != n) {
+ throw new IllegalStateException("Number of documents has changed!");
+ }
+
+ }
+ }
+
+ public Document addVersion(Document sourceDocument, String area, String language,
+ boolean addToSiteStructure) throws DocumentBuildException, PublicationException {
+ Document document = addVersion(sourceDocument, area, language);
+
+ if (addToSiteStructure && sourceDocument.hasLink()) {
+ String path = sourceDocument.getPath();
+ boolean visible = sourceDocument.getLink().getNode().isVisible();
+ addToSiteManager(path, document, sourceDocument.getLink().getLabel(), visible);
+ }
+
+ return document;
+ }
+
+ public Document addVersion(Document sourceDocument, String area, String language)
+ throws DocumentBuildException, DocumentException, PublicationException {
+ Document document = add(sourceDocument.getResourceType(), sourceDocument.getUUID(),
+ sourceDocument.getInputStream(), sourceDocument.getPublication(), area, language,
+ sourceDocument.getSourceExtension(), sourceDocument.getMimeType());
+ copyMetaData(sourceDocument, document);
+
+ return document;
+ }
+
+ public SourceResolver getSourceResolver() {
+ return sourceResolver;
+ }
+
+ public void setSourceResolver(SourceResolver sourceResolver) {
+ this.sourceResolver = sourceResolver;
+ }
+
+ public UUIDGenerator getUuidGenerator() {
+ return uuidGenerator;
+ }
+
+ public void setUuidGenerator(UUIDGenerator uuidGenerator) {
+ this.uuidGenerator = uuidGenerator;
+ }
+
+ public NodeFactory getNodeFactory() {
+ return nodeFactory;
+ }
+
+ public void setNodeFactory(NodeFactory nodeFactory) {
+ this.nodeFactory = nodeFactory;
+ }
+
+}