001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.lenya.cms.site.tree;
020:
021: import java.util.ArrayList;
022: import java.util.List;
023:
024: import org.apache.avalon.framework.container.ContainerUtil;
025: import org.apache.avalon.framework.logger.AbstractLogEnabled;
026: import org.apache.avalon.framework.logger.Logger;
027: import org.apache.lenya.cms.publication.DocumentException;
028: import org.apache.lenya.cms.publication.DocumentFactory;
029: import org.apache.lenya.cms.site.Link;
030: import org.apache.lenya.cms.site.SiteException;
031: import org.apache.lenya.cms.site.SiteNode;
032: import org.apache.lenya.cms.site.SiteStructure;
033: import org.apache.lenya.util.Assert;
034: import org.apache.lenya.xml.NamespaceHelper;
035: import org.w3c.dom.Element;
036: import org.w3c.dom.NamedNodeMap;
037: import org.w3c.dom.Node;
038: import org.w3c.dom.NodeList;
039:
040: /**
041: * Concrete implementation of the <code>SiteTreeNode</code> interface.
042: *
043: * @see org.apache.lenya.cms.site.tree.SiteTreeNode
044: * @version $Id: SiteTreeNodeImpl.java 155316 2005-02-25 10:53:29Z andreas $
045: */
046: public class SiteTreeNodeImpl extends AbstractLogEnabled implements
047: SiteTreeNode {
048:
049: /**
050: * <code>ID_ATTRIBUTE_NAME</code> The id attribute
051: */
052: public static final String ID_ATTRIBUTE_NAME = "id";
053: /**
054: * <code>UUID_ATTRIBUTE_NAME</code> The uuid attribute
055: */
056: public static final String UUID_ATTRIBUTE_NAME = "uuid";
057: /**
058: * <code>ISIBLEINNAV_ATTRIBUTE_NAME</code>The visibleinnav attribute
059: */
060: public static final String VISIBLEINNAV_ATTRIBUTE_NAME = "visibleinnav";
061: /**
062: * <code>HREF_ATTRIBUTE_NAME</code> The href attribute
063: */
064: public static final String HREF_ATTRIBUTE_NAME = "href";
065: /**
066: * <code>SUFFIX_ATTRIBUTE_NAME</code> The suffix attribute
067: */
068: public static final String SUFFIX_ATTRIBUTE_NAME = "suffix";
069: /**
070: * <code>LINK_ATTRIBUTE_NAME</code> The link attribute
071: */
072: public static final String LINK_ATTRIBUTE_NAME = "link";
073: /**
074: * <code>LANGUAGE_ATTRIBUTE_NAME</code> The language attribute
075: */
076: public static final String LANGUAGE_ATTRIBUTE_NAME = "xml:lang";
077: /**
078: * <code>NODE_NAME</code> The node name
079: */
080: public static final String NODE_NAME = "node";
081: /**
082: * <code>LABEL_NAME</code> The label name
083: */
084: public static final String LABEL_NAME = "label";
085:
086: private Element node = null;
087: private DefaultSiteTree tree;
088:
089: private DocumentFactory factory;
090:
091: /**
092: * Creates a new SiteTreeNodeImpl object.
093: * @param factory The document factory.
094: * @param tree The tree.
095: * @param node The node.
096: * @param logger The logger.
097: *
098: * @param _node the node which is to be wrapped by this SiteTreeNode
099: */
100: protected SiteTreeNodeImpl(DocumentFactory factory,
101: DefaultSiteTree tree, Element node, Logger logger) {
102: ContainerUtil.enableLogging(this , logger);
103: this .node = node;
104: this .tree = tree;
105: this .factory = factory;
106: }
107:
108: public String getName() {
109: if (this .node == this .node.getOwnerDocument()
110: .getDocumentElement()) {
111: return "";
112: }
113: return this .node.getAttributes()
114: .getNamedItem(ID_ATTRIBUTE_NAME).getNodeValue();
115: }
116:
117: public String getUuid() {
118: if (this .node == this .node.getOwnerDocument()
119: .getDocumentElement()) {
120: getLogger().warn("Node equals OwnerDocument: " + this );
121: return "";
122: }
123: Element element = (Element) this .node;
124: if (element.hasAttribute(UUID_ATTRIBUTE_NAME)) {
125: return element.getAttribute(UUID_ATTRIBUTE_NAME);
126: } else {
127: if (getLanguages().length > 0) {
128: String path = getPath();
129: getLogger()
130: .warn(
131: "Assuming non-UUID content because no 'uuid' attribute is set"
132: + " and the node contains links. Returning path ["
133: + path + "] as UUID.");
134: return path;
135: } else {
136: return null;
137: }
138: }
139: }
140:
141: public String getPath() {
142: String absoluteId = "";
143: Node currentNode = this .node;
144: NamedNodeMap attributes = null;
145: Node idAttribute = null;
146:
147: while (currentNode != null) {
148: attributes = currentNode.getAttributes();
149:
150: if (attributes == null) {
151: break;
152: }
153:
154: idAttribute = attributes.getNamedItem(ID_ATTRIBUTE_NAME);
155:
156: if (idAttribute == null) {
157: break;
158: }
159:
160: absoluteId = "/" + idAttribute.getNodeValue() + absoluteId;
161: currentNode = currentNode.getParentNode();
162: }
163:
164: return absoluteId;
165: }
166:
167: protected SiteTreeLink[] getLinks() {
168: ArrayList labels = new ArrayList();
169:
170: NodeList children = this .node.getChildNodes();
171:
172: for (int i = 0; i < children.getLength(); i++) {
173: Node child = children.item(i);
174:
175: if ((child.getNodeType() == Node.ELEMENT_NODE)
176: && child.getNodeName().equals(LABEL_NAME)) {
177: String labelLanguage = null;
178: Node languageAttribute = child.getAttributes()
179: .getNamedItem(LANGUAGE_ATTRIBUTE_NAME);
180:
181: if (languageAttribute != null) {
182: labelLanguage = languageAttribute.getNodeValue();
183: }
184:
185: labels.add(new SiteTreeLink(this .factory, this ,
186: labelLanguage, (Element) child));
187: }
188: }
189:
190: return (SiteTreeLink[]) labels.toArray(new SiteTreeLink[labels
191: .size()]);
192: }
193:
194: public void addLabel(String language, String label)
195: throws SiteException {
196: Assert.isTrue("not contains " + language, !hasLink(language));
197:
198: NamespaceHelper helper = getNamespaceHelper();
199: Element labelElem = helper.createElement(
200: SiteTreeNodeImpl.LABEL_NAME, label);
201: labelElem.setAttribute(
202: SiteTreeNodeImpl.LANGUAGE_ATTRIBUTE_NAME, language);
203: node.insertBefore(labelElem, node.getFirstChild());
204: getTree().changed();
205: }
206:
207: public void removeLabel(String language) {
208: if (!hasLink(language)) {
209: throw new RuntimeException(this
210: + " does not contain the language [" + language
211: + "]");
212: } else {
213: // this node doesn't contain this label
214:
215: try {
216: NodeList children = this .node.getChildNodes();
217:
218: for (int i = 0; i < children.getLength(); i++) {
219: Node child = children.item(i);
220:
221: if ((child.getNodeType() == Node.ELEMENT_NODE)
222: && child.getNodeName().equals(LABEL_NAME)) {
223:
224: Node languageAttribute = child.getAttributes()
225: .getNamedItem(LANGUAGE_ATTRIBUTE_NAME);
226:
227: if (languageAttribute != null
228: && languageAttribute.getNodeValue()
229: .equals(language)) {
230: this .node.removeChild(child);
231: getTree().changed();
232: break;
233: }
234: }
235: }
236: deleteIfEmpty();
237: } catch (SiteException e) {
238: throw new RuntimeException(
239: "could not save sitetree after deleting label of "
240: + this + " [" + language + "]");
241: }
242: }
243:
244: }
245:
246: protected void deleteIfEmpty() throws SiteException {
247: if (getLanguages().length == 0 && getChildren().length == 0) {
248: delete();
249: }
250: }
251:
252: /**
253: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#visibleInNav()
254: */
255: public boolean visibleInNav() {
256: Node attribute = this .node.getAttributes().getNamedItem(
257: VISIBLEINNAV_ATTRIBUTE_NAME);
258:
259: if (attribute != null) {
260: return attribute.getNodeValue().equals("true");
261: }
262: return true;
263: }
264:
265: /**
266: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#getHref()
267: */
268: public String getHref() {
269: Node attribute = this .node.getAttributes().getNamedItem(
270: HREF_ATTRIBUTE_NAME);
271:
272: if (attribute != null) {
273: return attribute.getNodeValue();
274: }
275: return null;
276: }
277:
278: /**
279: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#getSuffix()
280: */
281: public String getSuffix() {
282: Node attribute = this .node.getAttributes().getNamedItem(
283: SUFFIX_ATTRIBUTE_NAME);
284:
285: if (attribute != null) {
286: return attribute.getNodeValue();
287: }
288: return null;
289: }
290:
291: /**
292: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#hasLink()
293: */
294: public boolean hasLink() {
295: Node attribute = this .node.getAttributes().getNamedItem(
296: LINK_ATTRIBUTE_NAME);
297:
298: if (attribute != null) {
299: return attribute.getNodeValue().equals("true");
300: }
301: return false;
302: }
303:
304: /**
305: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#getChildren()
306: */
307: public SiteNode[] getChildren() {
308: List childElements = new ArrayList();
309:
310: NamespaceHelper helper = getNamespaceHelper();
311: Element[] elements = helper.getChildren((Element) this .node,
312: SiteTreeNodeImpl.NODE_NAME);
313:
314: for (int i = 0; i < elements.length; i++) {
315: SiteTreeNode newNode = new SiteTreeNodeImpl(this .factory,
316: getTree(), elements[i], getLogger());
317: childElements.add(newNode);
318: }
319:
320: return (SiteNode[]) childElements
321: .toArray(new SiteNode[childElements.size()]);
322: }
323:
324: /**
325: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#removeChildren()
326: */
327: public SiteTreeNode[] removeChildren() {
328: List childElements = new ArrayList();
329: NamespaceHelper helper = getNamespaceHelper();
330: Element[] elements = helper.getChildren((Element) this .node,
331: SiteTreeNodeImpl.NODE_NAME);
332: for (int i = 0; i < elements.length; i++) {
333: this .node.removeChild(elements[i]);
334: SiteTreeNode newNode = new SiteTreeNodeImpl(this .factory,
335: getTree(), elements[i], getLogger());
336: childElements.add(newNode);
337: }
338: return (SiteTreeNode[]) childElements
339: .toArray(new SiteTreeNode[childElements.size()]);
340: }
341:
342: /**
343: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#getNextSiblings()
344: */
345: public SiteTreeNode[] getNextSiblings() {
346: List siblingElements = new ArrayList();
347:
348: NamespaceHelper helper = getNamespaceHelper();
349: Element[] elements = helper.getNextSiblings(
350: (Element) this .node, SiteTreeNodeImpl.NODE_NAME);
351:
352: for (int i = 0; i < elements.length; i++) {
353: SiteTreeNode newNode = new SiteTreeNodeImpl(this .factory,
354: getTree(), elements[i], getLogger());
355: siblingElements.add(newNode);
356: }
357:
358: return (SiteTreeNode[]) siblingElements
359: .toArray(new SiteTreeNode[siblingElements.size()]);
360: }
361:
362: /**
363: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#getPrecedingSiblings()
364: */
365: public SiteTreeNode[] getPrecedingSiblings() {
366: List siblingElements = new ArrayList();
367:
368: NamespaceHelper helper = getNamespaceHelper();
369: Element[] elements = helper.getPrecedingSiblings(
370: (Element) this .node, SiteTreeNodeImpl.NODE_NAME);
371:
372: for (int i = 0; i < elements.length; i++) {
373: SiteTreeNode newNode = new SiteTreeNodeImpl(this .factory,
374: getTree(), elements[i], getLogger());
375: siblingElements.add(newNode);
376: }
377:
378: return (SiteTreeNode[]) siblingElements
379: .toArray(new SiteTreeNode[siblingElements.size()]);
380: }
381:
382: /**
383: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#getNextSiblingDocumentId()
384: */
385: public String getNextSiblingDocumentId() {
386: SiteTreeNode[] siblings = getNextSiblings();
387: if (siblings != null && siblings.length > 0) {
388: return siblings[0].getPath();
389: }
390: return null;
391: }
392:
393: /**
394: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#accept(org.apache.lenya.cms.site.tree.SiteTreeNodeVisitor)
395: */
396: public void accept(SiteTreeNodeVisitor visitor)
397: throws DocumentException {
398: visitor.visitSiteTreeNode(this );
399: }
400:
401: /**
402: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#acceptSubtree(org.apache.lenya.cms.site.tree.SiteTreeNodeVisitor)
403: */
404: public void acceptSubtree(SiteTreeNodeVisitor visitor)
405: throws DocumentException {
406: this .accept(visitor);
407: SiteNode[] children = this .getChildren();
408: if (children == null) {
409: getLogger().info(
410: "The node " + getPath() + " has no children");
411: return;
412: }
413: for (int i = 0; i < children.length; i++) {
414: ((SiteTreeNodeImpl) children[i]).acceptSubtree(visitor);
415: }
416: }
417:
418: protected void acceptReverseSubtree(SiteTreeNodeVisitor visitor)
419: throws DocumentException {
420: List orderedNodes = this .postOrder();
421: for (int i = 0; i < orderedNodes.size(); i++) {
422: SiteTreeNodeImpl _node = (SiteTreeNodeImpl) orderedNodes
423: .get(i);
424: _node.accept(visitor);
425: }
426: }
427:
428: /**
429: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#postOrder()
430: */
431: public List postOrder() {
432: List list = new ArrayList();
433: SiteNode[] children = this .getChildren();
434: for (int i = 0; i < children.length; i++) {
435: List orderedChildren = ((SiteTreeNodeImpl) children[i])
436: .postOrder();
437: list.addAll(orderedChildren);
438: }
439: list.add(this );
440: return list;
441: }
442:
443: /**
444: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#setNodeAttribute(String, String)
445: */
446: public void setNodeAttribute(String attributeName,
447: String attributeValue) {
448: Element element = (Element) this .node;
449: element.setAttribute(attributeName, attributeValue);
450: }
451:
452: /**
453: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#getChildren(java.lang.String)
454: */
455: public SiteTreeNode[] getChildren(String language) {
456: SiteNode[] children = getChildren();
457: List languageChildren = new ArrayList();
458:
459: for (int i = 0; i < children.length; i++) {
460: if (children[i].hasLink(language)) {
461: languageChildren.add(children[i]);
462: }
463: }
464:
465: return (SiteTreeNode[]) languageChildren
466: .toArray(new SiteTreeNode[languageChildren.size()]);
467: }
468:
469: /**
470: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#getParent()
471: */
472: public SiteNode getParent() throws SiteException {
473: SiteTreeNode parent = null;
474:
475: Node parentNode = this .node.getParentNode();
476: if (parentNode.getNodeType() == Node.ELEMENT_NODE
477: && parentNode.getLocalName().equals(NODE_NAME)) {
478: parent = new SiteTreeNodeImpl(this .factory, getTree(),
479: (Element) parentNode, getLogger());
480: ContainerUtil.enableLogging(parent, getLogger());
481: } else {
482: throw new SiteException("The node [" + this
483: + "] has no parent.");
484: }
485:
486: return parent;
487: }
488:
489: /**
490: * Returns the namespace helper of the sitetree XML document.
491: * @return A namespace helper.
492: */
493: protected NamespaceHelper getNamespaceHelper() {
494: NamespaceHelper helper = new NamespaceHelper(
495: DefaultSiteTree.NAMESPACE_URI, "", this .node
496: .getOwnerDocument());
497: return helper;
498: }
499:
500: /**
501: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#getParent(java.lang.String)
502: */
503: public SiteTreeNode getParent(String language) {
504: SiteTreeNode parent;
505: try {
506: parent = (SiteTreeNode) getParent();
507: } catch (SiteException e) {
508: throw new RuntimeException(e);
509: }
510: if (!parent.hasLink(language)) {
511: parent = null;
512: }
513: return parent;
514: }
515:
516: /**
517: * @see org.apache.lenya.cms.site.tree.SiteTreeNode#preOrder()
518: */
519: public List preOrder() {
520: List list = new ArrayList();
521: list.add(this );
522: SiteNode[] children = this .getChildren();
523: for (int i = 0; i < children.length; i++) {
524: List orderedChildren = ((SiteTreeNodeImpl) children[i])
525: .preOrder();
526: list.addAll(orderedChildren);
527: }
528: return list;
529: }
530:
531: public String getNodeAttribute(String attributeName) {
532: Element element = (Element) this .node;
533: return element.getAttribute(attributeName);
534: }
535:
536: public void setUUID(String uuid) {
537: setNodeAttribute(UUID_ATTRIBUTE_NAME, uuid);
538: }
539:
540: public SiteStructure getStructure() {
541: return getTree();
542: }
543:
544: protected DefaultSiteTree getTree() {
545: return this .tree;
546: }
547:
548: public String[] getLanguages() {
549: Link[] links = getLinks();
550: String[] languages = new String[links.length];
551: for (int i = 0; i < links.length; i++) {
552: languages[i] = links[i].getLanguage();
553: }
554: return languages;
555: }
556:
557: public Link getLink(String language) throws SiteException {
558: Link link = getLinkInternal(language);
559: if (link == null) {
560: throw new SiteException("The node [" + this
561: + "] doesn't contain the language [" + language
562: + "].");
563: }
564: return link;
565: }
566:
567: protected SiteTreeLink getLinkInternal(String language) {
568: SiteTreeLink[] links = getLinks();
569: for (int i = 0; i < links.length; i++) {
570: if (links[i].getLanguage().equals(language)) {
571: return links[i];
572: }
573: }
574: return null;
575: }
576:
577: public boolean hasLink(String language) {
578: return getLinkInternal(language) != null;
579: }
580:
581: public boolean equals(Object obj) {
582: if (!getClass().isInstance(obj)) {
583: return false;
584: }
585: return getKey().equals(((SiteTreeNodeImpl) obj).getKey());
586: }
587:
588: protected String getKey() {
589: return getTree().getPublication().getId() + ":"
590: + getTree().getArea() + ":" + getPath();
591: }
592:
593: public int hashCode() {
594: return getKey().hashCode();
595: }
596:
597: public String toString() {
598: return getKey();
599: }
600:
601: public boolean isVisible() {
602: String value = getNodeAttribute(SiteTreeNodeImpl.VISIBLEINNAV_ATTRIBUTE_NAME);
603: if (value != null && !value.equals("")) {
604: return Boolean.valueOf(value).booleanValue();
605: } else {
606: return true;
607: }
608: }
609:
610: public synchronized void setVisible(boolean visibleInNav) {
611: if (visibleInNav) {
612: setNodeAttribute(
613: SiteTreeNodeImpl.VISIBLEINNAV_ATTRIBUTE_NAME,
614: "true");
615: } else {
616: setNodeAttribute(
617: SiteTreeNodeImpl.VISIBLEINNAV_ATTRIBUTE_NAME,
618: "false");
619: }
620: save();
621: }
622:
623: public void delete() {
624: try {
625: SiteTreeNodeImpl parent = null;
626: if (!isTopLevel()) {
627: parent = (SiteTreeNodeImpl) getParent();
628: }
629: getTree().removeNode(getPath());
630: if (parent != null) {
631: parent.deleteIfEmpty();
632: }
633: } catch (SiteException e) {
634: throw new RuntimeException(e);
635: }
636: }
637:
638: public boolean isTopLevel() {
639: return getPath().lastIndexOf("/") == 0;
640: }
641:
642: protected void save() {
643: try {
644: ((DefaultSiteTree) getTree()).saveDocument();
645: } catch (SiteException e) {
646: throw new RuntimeException(e);
647: }
648: }
649:
650: }
|