blob: b7d404af980a3c93216d4ab1b74db9db1489cc4c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.lenya.cms.site.tree;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.apache.avalon.framework.container.ContainerUtil;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.lenya.cms.publication.DocumentFactory;
import org.apache.lenya.cms.publication.Publication;
import org.apache.lenya.cms.repository.NodeFactory;
import org.apache.lenya.cms.repository.RepositoryException;
import org.apache.lenya.cms.repository.Session;
import org.apache.lenya.cms.site.Link;
import org.apache.lenya.cms.site.SiteException;
import org.apache.lenya.cms.site.SiteNode;
import org.apache.lenya.util.Assert;
import org.apache.lenya.xml.DocumentHelper;
import org.apache.lenya.xml.NamespaceHelper;
import org.apache.xpath.XPathAPI;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Default sitetree implementation.
*
* @version $Id: DefaultSiteTree.java 208764 2005-07-01 15:57:21Z andreas $
*/
public class DefaultSiteTree extends AbstractLogEnabled implements SiteTree {
/**
* The sitetree namespace.
*/
public static final String NAMESPACE_URI = "http://apache.org/cocoon/lenya/sitetree/1.0";
/**
* The name of the sitetree file.
*/
public static final String SITE_TREE_FILENAME = "sitetree.xml";
private String sourceUri;
// the area is only retained to provide some more info when raising an
// exception.
private String area = "";
private Publication pub;
protected ServiceManager manager;
private Document document;
private DocumentFactory factory;
private org.apache.lenya.cms.repository.Node repositoryNode;
private boolean changed;
/**
* Create a DefaultSiteTree
* @param factory The document factory.
* @param publication The publication.
* @param _area The area.
* @param manager The service manager.
* @param logger The logger.
* @throws SiteException if an error occurs.
*/
protected DefaultSiteTree(DocumentFactory factory, Publication publication, String _area,
ServiceManager manager, Logger logger) throws SiteException {
ContainerUtil.enableLogging(this, logger);
this.factory = factory;
this.pub = publication;
this.sourceUri = publication.getSourceURI() + "/content/" + _area + "/"
+ SITE_TREE_FILENAME;
this.area = _area;
this.manager = manager;
try {
if (getRepositoryNode().exists()) {
this.document = DocumentHelper.readDocument(getRepositoryNode().getInputStream());
}
else {
getLogger().info("Empty sitetree will be created/initialized!");
this.document = createDocument();
}
} catch (Exception e) {
throw new SiteException(e);
}
}
protected void saveDocument() throws SiteException {
try {
DocumentHelper.writeDocument(this.document, getRepositoryNode().getOutputStream());
} catch (Exception e) {
throw new SiteException(e);
}
}
/**
* Checks if the tree file has been modified externally and reloads the site
* tree. protected synchronized void checkModified() { if
* (this.area.equals(Publication.LIVE_AREA) && this.treefile.lastModified() >
* this.lastModified) {
*
* if (getLogger().isDebugEnabled()) { getLogger().debug("Sitetree [" +
* this.treefile + "] has changed: reloading."); }
*
* try { this.document = DocumentHelper.readDocument(this.treefile); } catch
* (Exception e) { throw new IllegalStateException(e.getMessage()); }
* this.lastModified = this.treefile.lastModified(); } }
*/
/**
* Create a new DefaultSiteTree xml document.
* @return the new site document
* @throws ParserConfigurationException if an error occurs
*/
public synchronized Document createDocument() throws ParserConfigurationException {
Document document = DocumentHelper.createDocument(NAMESPACE_URI, "site", null);
Element root = document.getDocumentElement();
root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
root
.setAttribute("xsi:schemaLocation",
"http://apache.org/cocoon/lenya/sitetree/1.0 ../../../../resources/entities/sitetree.xsd");
return document;
}
/**
* Find a node in a subtree. The search is started at the given node. The
* list of ids contains the document-id split by "/".
* @param node where to start the search
* @param ids list of node ids
* @return the node that matches the path given in the list of ids
*/
protected synchronized Node findNode(Node node, List ids) {
if (ids.size() < 1) {
return node;
}
NodeList nodes = node.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
NamedNodeMap attributes = nodes.item(i).getAttributes();
if (attributes != null) {
Node idAttribute = attributes.getNamedItem("id");
if (idAttribute != null && !"".equals(idAttribute.getNodeValue())
&& idAttribute.getNodeValue().equals(ids.get(0))) {
return findNode(nodes.item(i), ids.subList(1, ids.size()));
}
}
}
// node wasn't found
return null;
}
protected synchronized void addNode(SiteTreeNode node, String refpath) throws SiteException {
SiteTreeNode target = addNode(node.getParent().getPath(), node.getName(), node.getUuid(),
node.isVisible(), node.getHref(), node.getSuffix(), node.hasLink(), refpath);
copyLinks(node, target);
}
protected void copyLinks(SiteTreeNode source, SiteTreeNode target) throws SiteException {
String[] languages = source.getLanguages();
for (int i = 0; i < languages.length; i++) {
addLabel(target.getPath(), languages[i], source.getLink(languages[i]).getLabel());
}
}
protected synchronized void addNode(String parentid, String id, String uuid, boolean visibleInNav)
throws SiteException {
addNode(parentid, id, uuid, visibleInNav, null, null, false);
}
protected synchronized void addNode(SiteTreeNode node) throws SiteException {
addNode(node, null);
}
protected synchronized SiteTreeNodeImpl addNode(String path, String uuid, boolean visibleInNav,
String href, String suffix, boolean link, String refpath) throws SiteException {
StringBuffer buf = new StringBuffer();
StringTokenizer st = new StringTokenizer(path, "/");
int length = st.countTokens();
for (int i = 0; i < (length - 1); i++) {
buf.append("/" + st.nextToken());
}
String parentid = buf.toString();
String id = st.nextToken();
return addNode(parentid, id, uuid, visibleInNav, href, suffix, link, refpath);
}
protected synchronized SiteTreeNodeImpl addNode(String path, String uuid, boolean visibleInNav,
String href, String suffix, boolean link) throws SiteException {
return addNode(path, uuid, visibleInNav, href, suffix, link, null);
}
protected synchronized SiteTreeNodeImpl addNode(String parentid, String id, String uuid,
boolean visibleInNav, String href, String suffix, boolean link) throws SiteException {
return addNode(parentid + "/" + id, uuid, visibleInNav, href, suffix, link, null);
}
protected void createParents(final String path) throws SiteException {
String[] steps = path.substring(1).split("/");
int s = 0;
String ancestorPath = "";
while (s < steps.length) {
if (!contains(ancestorPath)) {
add(ancestorPath);
}
ancestorPath += "/" + steps[s];
s++;
}
}
protected synchronized SiteTreeNodeImpl addNode(String parentPath, String name, String uuid,
boolean visibleInNav, String href, String suffix, boolean link, String refpath)
throws SiteException {
String path = parentPath + "/" + name;
createParents(path);
Node parentNode = getNodeInternal(parentPath);
getLogger().debug("PARENT ELEMENT: " + parentNode);
getLogger().debug("VISIBLEINNAV IS: " + visibleInNav);
// Check if child already exists
Node childNode = getNodeInternal(path);
if (childNode != null) {
getLogger().info("This node: " + path + " has already been inserted");
return (SiteTreeNodeImpl) getNode(path);
}
// Create node
NamespaceHelper helper = new NamespaceHelper(NAMESPACE_URI, "", this.document);
Element child = helper.createElement(SiteTreeNodeImpl.NODE_NAME);
child.setAttribute(SiteTreeNodeImpl.ID_ATTRIBUTE_NAME, name);
if (uuid != null) {
child.setAttribute(SiteTreeNodeImpl.UUID_ATTRIBUTE_NAME, uuid);
}
if (visibleInNav) {
child.setAttribute(SiteTreeNodeImpl.VISIBLEINNAV_ATTRIBUTE_NAME, "true");
} else {
child.setAttribute(SiteTreeNodeImpl.VISIBLEINNAV_ATTRIBUTE_NAME, "false");
}
if ((href != null) && (href.length() > 0)) {
child.setAttribute(SiteTreeNodeImpl.HREF_ATTRIBUTE_NAME, href);
}
if ((suffix != null) && (suffix.length() > 0)) {
child.setAttribute(SiteTreeNodeImpl.SUFFIX_ATTRIBUTE_NAME, suffix);
}
if (link) {
child.setAttribute(SiteTreeNodeImpl.LINK_ATTRIBUTE_NAME, "true");
}
// Add Node
if (refpath != null && !refpath.equals("")) {
Node nextSibling = getNodeInternal(refpath);
if (nextSibling != null) {
parentNode.insertBefore(child, nextSibling);
} else {
parentNode.appendChild(child);
}
} else {
parentNode.appendChild(child);
}
getLogger().debug("Tree has been modified: " + document.getDocumentElement());
saveDocument();
return (SiteTreeNodeImpl) getNode(path);
}
protected synchronized void addLabel(String path, String language, String label) {
try {
SiteTreeNodeImpl node = (SiteTreeNodeImpl) getNode(path);
if (node != null) {
node.addLabel(language, label);
}
saveDocument();
} catch (SiteException e) {
throw new RuntimeException(e);
}
}
protected synchronized void removeLabel(String path, String language) {
try {
SiteTreeNodeImpl node = (SiteTreeNodeImpl) getNode(path);
node.removeLabel(language);
saveDocument();
} catch (SiteException e) {
throw new RuntimeException(e);
}
}
protected synchronized SiteTreeNode removeNode(String path) {
assert path != null;
Node node;
try {
node = removeNodeInternal(path);
} catch (SiteException e) {
throw new RuntimeException(e);
}
if (node == null) {
return null;
}
SiteTreeNode newNode = new SiteTreeNodeImpl(this.factory, this, (Element) node, getLogger());
ContainerUtil.enableLogging(newNode, getLogger());
return newNode;
}
/**
* removes the node corresponding to the given document-id and returns it
* @param path the document-id of the Node to be removed
* @return the <code>Node</code> that was removed
* @throws SiteException
*/
private synchronized Node removeNodeInternal(String path) throws SiteException {
Assert.isTrue("contains " + path, contains(path));
Node node = this.getNodeInternal(path);
Node parentNode = node.getParentNode();
Node newNode = parentNode.removeChild(node);
try {
saveDocument();
} catch (SiteException e) {
throw new RuntimeException(e);
}
return newNode;
}
/**
* Find a node for a given document-id
*
* @param path the document-id of the Node that we're trying to get
*
* @return the Node if there is a Node for the given document-id, null
* otherwise
* @throws SiteException
*/
private synchronized Node getNodeInternal(String path) throws SiteException {
StringTokenizer st = new StringTokenizer(path, "/");
ArrayList ids = new ArrayList();
while (st.hasMoreTokens()) {
ids.add(st.nextToken());
}
Node node = findNode(this.document.getDocumentElement(), ids);
return node;
}
/**
* @see org.apache.lenya.cms.site.tree.SiteTree#getNode(java.lang.String)
*/
public synchronized SiteNode getNode(String path) throws SiteException {
assert path != null;
SiteTreeNode treeNode = null;
Node node;
try {
node = getNodeInternal(path);
} catch (SiteException e) {
throw new RuntimeException(e);
}
if (node != null) {
treeNode = new SiteTreeNodeImpl(this.factory, this, (Element) node, getLogger());
ContainerUtil.enableLogging(treeNode, getLogger());
} else {
throw new SiteException("No node contained for path [" + path + "]!");
}
return treeNode;
}
/**
* Move up the node amongst its siblings.
* @param path The document id for the node.
* @throws SiteException if the moving failed.
*/
public synchronized void moveUp(String path) throws SiteException {
Node node = this.getNodeInternal(path);
if (node == null) {
throw new SiteException("Node to move: " + path + " not found");
}
Node parentNode = node.getParentNode();
if (parentNode == null) {
throw new SiteException("Parentid of node with path: " + path + " not found");
}
Node previousNode;
try {
previousNode = XPathAPI.selectSingleNode(node,
"(preceding-sibling::*[local-name() = 'node'])[last()]");
} catch (TransformerException e) {
throw new SiteException(e);
}
if (previousNode == null) {
getLogger().warn("Couldn't found a preceding sibling");
return;
}
Node insertNode = parentNode.removeChild(node);
parentNode.insertBefore(insertNode, previousNode);
saveDocument();
}
/**
* Move down the node amongst its siblings.
*
* @param path The document id for the node.
* @throws SiteException if the moving failed.
*/
public synchronized void moveDown(String path) throws SiteException {
Node node = this.getNodeInternal(path);
if (node == null) {
throw new SiteException("Node to move: " + path + " not found");
}
Node parentNode = node.getParentNode();
if (parentNode == null) {
throw new SiteException("Parentid of node with path: " + path + " not found");
}
Node nextNode;
try {
nextNode = XPathAPI.selectSingleNode(node,
"following-sibling::*[local-name() = 'node'][position()=2]");
} catch (TransformerException e) {
throw new SiteException(e);
}
Node insertNode = parentNode.removeChild(node);
if (nextNode == null) {
getLogger().debug("Couldn't found the second following sibling");
parentNode.appendChild(insertNode);
} else {
parentNode.insertBefore(insertNode, nextNode);
}
saveDocument();
}
protected synchronized void setLabel(String path, String language, String label) {
try {
SiteTreeNode node = (SiteTreeNode) getNode(path);
node.getLink(language).setLabel(label);
} catch (SiteException e) {
throw new RuntimeException(e);
}
}
/**
* @see org.apache.lenya.cms.site.SiteStructure#getRepositoryNode()
*/
public org.apache.lenya.cms.repository.Node getRepositoryNode() {
if (this.repositoryNode == null) {
Session session = this.getPublication().getFactory().getSession();
NodeFactory factory = null;
try {
factory = (NodeFactory) manager.lookup(NodeFactory.ROLE);
this.repositoryNode = (org.apache.lenya.cms.repository.Node)
session.getRepositoryItem(factory, this.sourceUri);
} catch (Exception e) {
throw new RuntimeException("Creating repository node failed: ", e);
} finally {
if (factory != null) {
manager.release(factory);
}
}
}
return this.repositoryNode;
}
public void save() throws RepositoryException {
try {
saveDocument();
} catch (SiteException e) {
throw new RepositoryException(e);
}
}
public String getArea() {
return this.area;
}
public Publication getPublication() {
return this.pub;
}
public boolean contains(String path) {
try {
return getNodeInternal(path) != null;
} catch (SiteException e) {
throw new RuntimeException(e);
}
}
public boolean containsByUuid(String uuid, String language) {
return getByUuidInternal(uuid, language) != null;
}
protected SiteNode getByUuidInternal(String uuid, String language) {
String xPath = "//*[@uuid = '" + uuid + "']";
SiteNode[] nodes = getNodesByXpath(xPath);
for (int i = 0; i < nodes.length; i++) {
if (nodes[i].hasLink(language)) {
return nodes[i];
}
}
return null;
}
protected SiteNode getNodeByXpath(String xPath) {
try {
Element element = (Element) XPathAPI.selectSingleNode(this.document, xPath);
if (element == null) {
return null;
} else {
return new SiteTreeNodeImpl(this.factory, this, element, getLogger());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected SiteNode[] getNodesByXpath(String xPath) {
try {
NodeList list = XPathAPI.selectNodeList(this.document, xPath);
SiteNode[] nodes = new SiteNode[list.getLength()];
for (int i = 0; i < nodes.length; i++) {
Element element = (Element) list.item(i);
nodes[i] = new SiteTreeNodeImpl(this.factory, this, element, getLogger());
}
return nodes;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Link getByUuid(String uuid, String language) throws SiteException {
SiteNode node = getByUuidInternal(uuid, language);
if (node == null) {
throw new SiteException("The link for [" + uuid + ":" + language
+ "] is not contained!");
}
return node.getLink(language);
}
protected DocumentFactory getFactory() {
return this.factory;
}
public Link add(String path, org.apache.lenya.cms.publication.Document doc)
throws SiteException {
if (contains(path)) {
SiteNode node = getNode(path);
if (node.getLanguages().length > 0 && !node.getUuid().equals(doc.getUUID())) {
throw new SiteException("Node for path [" + path + "] exists with different UUID!");
}
}
SiteTreeNodeImpl node = addNode(path, doc.getUUID(), true, null, "", false);
node.addLabel(doc.getLanguage(), "");
if (node.getLanguages().length == 1) {
node.setUUID(doc.getUUID());
}
return node.getLink(doc.getLanguage());
}
public SiteNode add(String path) throws SiteException {
SiteTreeNode node = addNode(path, null, true, null, "", false);
return node;
}
public boolean containsInAnyLanguage(String uuid) {
String xPath = "//*[@uuid = '" + uuid + "']";
return getNodeByXpath(xPath) != null;
}
public SiteNode[] getNodes() {
List nodes = getRootNode().preOrder();
nodes.remove(getRootNode());
return (SiteNode[]) nodes.toArray(new SiteNode[nodes.size()]);
}
public SiteNode add(String path, String followingSiblingPath) throws SiteException {
SiteTreeNode node = addNode(path, null, true, null, "", false, followingSiblingPath);
return node;
}
public SiteNode[] getTopLevelNodes() {
return getRootNode().getChildren();
}
protected SiteTreeNodeImpl getRootNode() {
SiteTreeNodeImpl root;
try {
root = (SiteTreeNodeImpl) getNode("/");
} catch (SiteException e) {
throw new RuntimeException(e);
}
return root;
}
public boolean contains(String path, String language) {
if (contains(path)) {
SiteNode node;
try {
node = getNode(path);
} catch (SiteException e) {
throw new RuntimeException(e);
}
return node.hasLink(language);
}
return false;
}
public Session getSession() {
return getRepositoryNode().getSession();
}
public SiteNode[] preOrder() {
List preOrder = getRootNode().preOrder();
return (SiteNode[]) preOrder.toArray(new SiteNode[preOrder.size()]);
}
public void changed() {
this.changed = true;
}
}