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.publication;
020:
021: import java.io.File;
022: import java.io.InputStream;
023: import java.io.OutputStream;
024: import java.util.ArrayList;
025: import java.util.Date;
026: import java.util.List;
027:
028: import org.apache.avalon.framework.container.ContainerUtil;
029: import org.apache.avalon.framework.logger.AbstractLogEnabled;
030: import org.apache.avalon.framework.logger.Logger;
031: import org.apache.avalon.framework.service.ServiceManager;
032: import org.apache.avalon.framework.service.ServiceSelector;
033: import org.apache.lenya.cms.cocoon.source.SourceUtil;
034: import org.apache.lenya.cms.metadata.MetaData;
035: import org.apache.lenya.cms.metadata.MetaDataException;
036: import org.apache.lenya.cms.publication.util.DocumentVisitor;
037: import org.apache.lenya.cms.repository.ContentHolder;
038: import org.apache.lenya.cms.repository.Node;
039: import org.apache.lenya.cms.repository.NodeFactory;
040: import org.apache.lenya.cms.repository.RepositoryException;
041: import org.apache.lenya.cms.repository.Session;
042: import org.apache.lenya.cms.site.Link;
043: import org.apache.lenya.cms.site.SiteException;
044: import org.apache.lenya.cms.site.SiteStructure;
045: import org.apache.lenya.util.Assert;
046:
047: /**
048: * A typical CMS document.
049: * @version $Id: DocumentImpl.java 595602 2007-11-16 09:18:02Z andreas $
050: */
051: public class DocumentImpl extends AbstractLogEnabled implements
052: Document {
053:
054: private DocumentIdentifier identifier;
055: private DocumentFactory factory;
056: protected ServiceManager manager;
057: private int revision = -1;
058:
059: /**
060: * The meta data namespace.
061: */
062: public static final String METADATA_NAMESPACE = "http://apache.org/lenya/metadata/document/1.0";
063:
064: /**
065: * The name of the resource type attribute. A resource has a resource type; this information can
066: * be used e.g. for different rendering of different types.
067: */
068: protected static final String METADATA_RESOURCE_TYPE = "resourceType";
069:
070: /**
071: * The name of the mime type attribute.
072: */
073: protected static final String METADATA_MIME_TYPE = "mimeType";
074:
075: /**
076: * The name of the content type attribute. Any content managed by Lenya has a type; this
077: * information can be used e.g. to provide an appropriate management interface.
078: */
079: protected static final String METADATA_CONTENT_TYPE = "contentType";
080:
081: /**
082: * The number of seconds from the request that a document can be cached before it expires
083: */
084: protected static final String METADATA_EXPIRES = "expires";
085:
086: /**
087: * The extension to use for the document source.
088: */
089: protected static final String METADATA_EXTENSION = "extension";
090:
091: /**
092: * Creates a new instance of DefaultDocument.
093: * @param manager The service manager.
094: * @param map The identity map the document belongs to.
095: * @param identifier The identifier.
096: * @param revision The revision number or -1 if the latest revision should be used.
097: * @param _logger a logger
098: */
099: protected DocumentImpl(ServiceManager manager, DocumentFactory map,
100: DocumentIdentifier identifier, int revision, Logger _logger) {
101:
102: ContainerUtil.enableLogging(this , _logger);
103: if (getLogger().isDebugEnabled()) {
104: getLogger().debug(
105: "DefaultDocument() creating new instance with id ["
106: + identifier.getUUID() + "], language ["
107: + identifier.getLanguage() + "]");
108: }
109:
110: if (identifier.getUUID() == null) {
111: throw new IllegalArgumentException(
112: "The UUID must not be null!");
113: }
114:
115: this .manager = manager;
116: this .identifier = identifier;
117: this .factory = map;
118: this .revision = revision;
119:
120: if (getLogger().isDebugEnabled()) {
121: getLogger().debug(
122: "DefaultDocument() done building instance with _id ["
123: + identifier.getUUID() + "], _language ["
124: + identifier.getLanguage() + "]");
125: }
126: }
127:
128: /**
129: * @see org.apache.lenya.cms.publication.Document#getExpires()
130: */
131: public Date getExpires() throws DocumentException {
132: Date expires = null;
133: long secs = 0;
134:
135: MetaData metaData = null;
136: String expiresMeta = null;
137: try {
138: metaData = this .getMetaData(METADATA_NAMESPACE);
139: expiresMeta = metaData.getFirstValue("expires");
140: } catch (MetaDataException e) {
141: throw new DocumentException(e);
142: }
143: if (expiresMeta != null) {
144: secs = Long.parseLong(expiresMeta);
145: } else {
146: secs = -1;
147: }
148:
149: if (secs != -1) {
150: Date date = new Date();
151: date.setTime(date.getTime() + secs * 1000l);
152: expires = date;
153: } else {
154: expires = this .getResourceType().getExpires();
155: }
156:
157: return expires;
158: }
159:
160: /**
161: * @see org.apache.lenya.cms.publication.Document#getName()
162: */
163: public String getName() {
164: try {
165: return getLink().getNode().getName();
166: } catch (DocumentException e) {
167: throw new RuntimeException(e);
168: }
169: }
170:
171: private Publication publication;
172:
173: /**
174: * @see org.apache.lenya.cms.publication.Document#getPublication()
175: */
176: public Publication getPublication() {
177: if (this .publication == null) {
178: try {
179: this .publication = getFactory().getPublication(
180: getIdentifier().getPublicationId());
181: } catch (PublicationException e) {
182: throw new RuntimeException(e);
183: }
184: }
185: return this .publication;
186: }
187:
188: /**
189: * @see org.apache.lenya.cms.publication.Document#getLastModified()
190: */
191: public long getLastModified() throws DocumentException {
192: try {
193: return getRepositoryNode().getLastModified();
194: } catch (RepositoryException e) {
195: throw new DocumentException(e);
196: }
197: }
198:
199: public File getFile() {
200: return getPublication().getPathMapper().getFile(
201: getPublication(), getArea(), getUUID(), getLanguage());
202: }
203:
204: /**
205: * @see org.apache.lenya.cms.publication.Document#getLanguage()
206: */
207: public String getLanguage() {
208: return this .identifier.getLanguage();
209: }
210:
211: /**
212: * @see org.apache.lenya.cms.publication.Document#getLanguages()
213: */
214: public String[] getLanguages() throws DocumentException {
215:
216: List documentLanguages = new ArrayList();
217: String[] allLanguages = getPublication().getLanguages();
218:
219: if (getLogger().isDebugEnabled()) {
220: getLogger().debug(
221: "Number of languages of this publication: "
222: + allLanguages.length);
223: }
224:
225: for (int i = 0; i < allLanguages.length; i++) {
226: if (existsTranslation(allLanguages[i])) {
227: documentLanguages.add(allLanguages[i]);
228: }
229: }
230:
231: return (String[]) documentLanguages
232: .toArray(new String[documentLanguages.size()]);
233: }
234:
235: /**
236: * @see org.apache.lenya.cms.publication.Document#getArea()
237: */
238: public String getArea() {
239: return this .identifier.getArea();
240: }
241:
242: private String extension = null;
243: private String defaultExtension = "html";
244:
245: /**
246: * @see org.apache.lenya.cms.publication.Document#getExtension()
247: */
248: public String getExtension() {
249: if (extension == null) {
250: String sourceExtension = getSourceExtension();
251: if (sourceExtension.equals("xml")
252: || sourceExtension.equals("")) {
253: getLogger().info(
254: "Default extension will be used: "
255: + defaultExtension);
256: return defaultExtension;
257: } else {
258: return sourceExtension;
259: }
260:
261: }
262: return this .extension;
263: }
264:
265: /**
266: * @see org.apache.lenya.cms.publication.Document#getUUID()
267: */
268: public String getUUID() {
269: return getIdentifier().getUUID();
270: }
271:
272: private String defaultSourceExtension = "xml";
273:
274: /**
275: * @see org.apache.lenya.cms.publication.Document#getSourceExtension()
276: */
277: public String getSourceExtension() {
278: String sourceExtension;
279: try {
280: sourceExtension = getMetaData(METADATA_NAMESPACE)
281: .getFirstValue(METADATA_EXTENSION);
282: } catch (Exception e) {
283: throw new RuntimeException(e);
284: }
285: if (sourceExtension == null) {
286: getLogger().warn(
287: "No source extension for document [" + this
288: + "]. The extension \""
289: + defaultSourceExtension
290: + "\" will be used as default!");
291: sourceExtension = defaultSourceExtension;
292: }
293: return sourceExtension;
294: }
295:
296: /**
297: * Sets the extension of the file in the URL.
298: * @param _extension A string.
299: */
300: protected void setExtension(String _extension) {
301: checkWritability();
302: assert _extension != null;
303: Assert.isTrue("Extension doesn't start with a dot", !_extension
304: .startsWith("."));
305: this .extension = _extension;
306: }
307:
308: /**
309: * @see org.apache.lenya.cms.publication.Document#exists()
310: */
311: public boolean exists() throws DocumentException {
312: try {
313: return getRepositoryNode().exists();
314: } catch (RepositoryException e) {
315: throw new DocumentException(e);
316: }
317: }
318:
319: /**
320: * @see org.apache.lenya.cms.publication.Document#existsInAnyLanguage()
321: */
322: public boolean existsInAnyLanguage() throws DocumentException {
323: String[] languages = getLanguages();
324:
325: if (languages.length > 0) {
326: if (getLogger().isDebugEnabled()) {
327: getLogger().debug(
328: "Document (" + this
329: + ") exists in at least one language: "
330: + languages.length);
331: }
332: String[] allLanguages = getPublication().getLanguages();
333: if (languages.length == allLanguages.length)
334: // TODO: This is not entirely true, because the publication
335: // could assume the
336: // languages EN and DE, but the document could exist for the
337: // languages DE and FR!
338: if (getLogger().isDebugEnabled()) {
339: getLogger()
340: .debug(
341: "Document ("
342: + this
343: + ") exists even in all languages of this publication");
344: }
345: return true;
346: } else {
347: if (getLogger().isDebugEnabled()) {
348: getLogger().debug(
349: "Document (" + this
350: + ") does NOT exist in any language");
351: }
352: return false;
353: }
354:
355: }
356:
357: public DocumentIdentifier getIdentifier() {
358: return this .identifier;
359: }
360:
361: /**
362: * @see java.lang.Object#equals(java.lang.Object)
363: */
364: public boolean equals(Object object) {
365: if (getClass().isInstance(object)) {
366: DocumentImpl document = (DocumentImpl) object;
367: return document.getIdentifier().equals(getIdentifier());
368: }
369: return false;
370: }
371:
372: /**
373: * @see java.lang.Object#hashCode()
374: */
375: public int hashCode() {
376:
377: String key = getPublication().getId() + ":"
378: + getPublication().getServletContext() + ":"
379: + getArea() + ":" + getUUID() + ":" + getLanguage();
380:
381: return key.hashCode();
382: }
383:
384: /**
385: * @see java.lang.Object#toString()
386: */
387: public String toString() {
388: return getIdentifier().toString();
389: }
390:
391: /**
392: * @see org.apache.lenya.cms.publication.Document#getFactory()
393: */
394: public DocumentFactory getFactory() {
395: return this .factory;
396: }
397:
398: /**
399: * @see org.apache.lenya.cms.publication.Document#getCanonicalWebappURL()
400: */
401: public String getCanonicalWebappURL() {
402: return "/" + getPublication().getId() + "/" + getArea()
403: + getCanonicalDocumentURL();
404: }
405:
406: /**
407: * @see org.apache.lenya.cms.publication.Document#getCanonicalDocumentURL()
408: */
409: public String getCanonicalDocumentURL() {
410: try {
411: DocumentBuilder builder = getPublication()
412: .getDocumentBuilder();
413: String webappUrl = builder.buildCanonicalUrl(getFactory(),
414: getLocator());
415: String prefix = "/" + getPublication().getId() + "/"
416: + getArea();
417: return webappUrl.substring(prefix.length());
418: } catch (Exception e) {
419: throw new RuntimeException(e);
420: }
421: }
422:
423: /**
424: * @see org.apache.lenya.cms.publication.Document#accept(org.apache.lenya.cms.publication.util.DocumentVisitor)
425: */
426: public void accept(DocumentVisitor visitor)
427: throws PublicationException {
428: visitor.visitDocument(this );
429: }
430:
431: /**
432: * @see org.apache.lenya.cms.publication.Document#delete()
433: */
434: public void delete() throws DocumentException {
435: if (hasLink()) {
436: throw new DocumentException("Can't delete document ["
437: + this
438: + "], it's still referenced in the site structure.");
439: }
440: try {
441: getRepositoryNode().delete();
442: } catch (Exception e) {
443: throw new DocumentException(e);
444: }
445: }
446:
447: protected static final String IDENTIFIABLE_TYPE = "document";
448:
449: private ResourceType resourceType;
450:
451: /**
452: * Convenience method to read the document's resource type from the meta-data.
453: * @see Document#getResourceType()
454: */
455: public ResourceType getResourceType() throws DocumentException {
456: if (this .resourceType == null) {
457: ServiceSelector selector = null;
458: try {
459: String name = getMetaData(METADATA_NAMESPACE)
460: .getFirstValue(METADATA_RESOURCE_TYPE);
461: if (name == null) {
462: throw new DocumentException(
463: "No resource type defined for document ["
464: + this + "]!");
465: }
466: selector = (ServiceSelector) this .manager
467: .lookup(ResourceType.ROLE + "Selector");
468: this .resourceType = (ResourceType) selector
469: .select(name);
470: } catch (Exception e) {
471: throw new DocumentException(e);
472: }
473: }
474: return this .resourceType;
475: }
476:
477: public MetaData getMetaData(String namespaceUri)
478: throws MetaDataException {
479: return getContentHolder().getMetaData(namespaceUri);
480: }
481:
482: public String[] getMetaDataNamespaceUris() throws MetaDataException {
483: return getContentHolder().getMetaDataNamespaceUris();
484: }
485:
486: public String getMimeType() throws DocumentException {
487: try {
488: String mimeType = getMetaData(METADATA_NAMESPACE)
489: .getFirstValue(METADATA_MIME_TYPE);
490: if (mimeType == null) {
491: mimeType = "";
492: }
493: return mimeType;
494: } catch (MetaDataException e) {
495: throw new DocumentException(e);
496: }
497: }
498:
499: public long getContentLength() throws DocumentException {
500: try {
501: return getContentHolder().getContentLength();
502: } catch (RepositoryException e) {
503: throw new DocumentException(e);
504: }
505: }
506:
507: public void setMimeType(String mimeType) throws DocumentException {
508: checkWritability();
509: try {
510: getMetaData(METADATA_NAMESPACE).setValue(
511: METADATA_MIME_TYPE, mimeType);
512: } catch (MetaDataException e) {
513: throw new DocumentException(e);
514: }
515: }
516:
517: public DocumentLocator getLocator() {
518: SiteStructure structure = area().getSite();
519: if (!structure.containsByUuid(getUUID(), getLanguage())) {
520: throw new RuntimeException("The document [" + this
521: + "] is not referenced in the site structure.");
522: }
523: try {
524: return DocumentLocator.getLocator(getPublication().getId(),
525: getArea(), structure.getByUuid(getUUID(),
526: getLanguage()).getNode().getPath(),
527: getLanguage());
528: } catch (SiteException e) {
529: throw new RuntimeException(e);
530: }
531: }
532:
533: public String getPath() throws DocumentException {
534: return getLink().getNode().getPath();
535: }
536:
537: public boolean existsAreaVersion(String area) {
538: String sourceUri = getSourceURI(getPublication(), area,
539: getUUID(), getLanguage());
540: try {
541: return SourceUtil.exists(sourceUri, this .manager);
542: } catch (Exception e) {
543: throw new RuntimeException(e);
544: }
545: }
546:
547: public boolean existsTranslation(String language) {
548: return area().contains(getUUID(), language);
549: }
550:
551: public Document getAreaVersion(String area)
552: throws DocumentException {
553: try {
554: return getFactory().get(getPublication(), area, getUUID(),
555: getLanguage());
556: } catch (DocumentBuildException e) {
557: throw new DocumentException(e);
558: }
559: }
560:
561: public Document getTranslation(String language)
562: throws DocumentException {
563: try {
564: return getFactory().get(getPublication(), getArea(),
565: getUUID(), language);
566: } catch (DocumentBuildException e) {
567: throw new DocumentException(e);
568: }
569: }
570:
571: private Node repositoryNode;
572:
573: /**
574: * @see org.apache.lenya.cms.publication.Document#getRepositoryNode()
575: */
576: public Node getRepositoryNode() {
577: if (this .repositoryNode == null) {
578: this .repositoryNode = getRepositoryNode(this .manager,
579: getFactory(), getSourceURI());
580: }
581: return this .repositoryNode;
582: }
583:
584: protected ContentHolder getContentHolder() {
585: Node node = getRepositoryNode();
586: if (isRevisionSpecified()) {
587: try {
588: return node.getHistory().getRevision(revision);
589: } catch (RepositoryException e) {
590: throw new RuntimeException(e);
591: }
592: } else {
593: return node;
594: }
595: }
596:
597: protected static Node getRepositoryNode(ServiceManager manager,
598: DocumentFactory docFactory, String sourceUri) {
599: Session session = docFactory.getSession();
600: NodeFactory factory = null;
601: try {
602: factory = (NodeFactory) manager.lookup(NodeFactory.ROLE);
603: return (Node) session.getRepositoryItem(factory, sourceUri);
604: } catch (Exception e) {
605: throw new RuntimeException(
606: "Creating repository node failed: ", e);
607: } finally {
608: if (factory != null) {
609: manager.release(factory);
610: }
611: }
612: }
613:
614: /**
615: * @see org.apache.lenya.cms.publication.Document#getSourceURI()
616: */
617: public String getSourceURI() {
618: return getSourceURI(getPublication(), getArea(), getUUID(),
619: getLanguage());
620: }
621:
622: protected static String getSourceURI(Publication pub, String area,
623: String uuid, String language) {
624: String path = pub.getPathMapper().getPath(uuid, language);
625: return pub.getSourceURI() + "/content/" + area + "/" + path;
626: }
627:
628: public boolean existsVersion(String area, String language) {
629: String sourceUri = getSourceURI(getPublication(), area,
630: getUUID(), language);
631: try {
632: return SourceUtil.exists(sourceUri, this .manager);
633: } catch (Exception e) {
634: throw new RuntimeException(e);
635: }
636: }
637:
638: public Document getVersion(String area, String language)
639: throws DocumentException {
640: try {
641: return getFactory().get(getPublication(), area, getUUID(),
642: language);
643: } catch (DocumentBuildException e) {
644: throw new DocumentException(e);
645: }
646: }
647:
648: public Link getLink() throws DocumentException {
649: SiteStructure structure = area().getSite();
650: try {
651: if (structure.containsByUuid(getUUID(), getLanguage())) {
652: return structure.getByUuid(getUUID(), getLanguage());
653: } else {
654: throw new DocumentException("The document [" + this
655: + "] is not referenced in the site structure ["
656: + structure + "].");
657: }
658: } catch (Exception e) {
659: throw new DocumentException(e);
660: }
661: }
662:
663: public boolean hasLink() {
664: return area().getSite()
665: .containsByUuid(getUUID(), getLanguage());
666: }
667:
668: public Area area() {
669: try {
670: return getPublication().getArea(getArea());
671: } catch (PublicationException e) {
672: throw new RuntimeException(e);
673: }
674: }
675:
676: public void setResourceType(ResourceType resourceType) {
677: checkWritability();
678: Assert.notNull("resource type", resourceType);
679: try {
680: MetaData meta = getMetaData(DocumentImpl.METADATA_NAMESPACE);
681: meta.setValue(DocumentImpl.METADATA_RESOURCE_TYPE,
682: resourceType.getName());
683: } catch (MetaDataException e) {
684: throw new RuntimeException(e);
685: }
686: }
687:
688: public void setSourceExtension(String extension) {
689: checkWritability();
690: Assert.notNull("extension", extension);
691: Assert.isTrue("extension doesn't start with a dot", !extension
692: .startsWith("."));
693: try {
694: MetaData meta = getMetaData(DocumentImpl.METADATA_NAMESPACE);
695: meta.setValue(DocumentImpl.METADATA_EXTENSION, extension);
696: } catch (MetaDataException e) {
697: throw new RuntimeException(e);
698: }
699: }
700:
701: public OutputStream getOutputStream() {
702: checkWritability();
703: try {
704: return getRepositoryNode().getOutputStream();
705: } catch (RepositoryException e) {
706: throw new RuntimeException(e);
707: }
708: }
709:
710: protected void checkWritability() {
711: if (isRevisionSpecified()) {
712: throw new UnsupportedOperationException();
713: }
714: }
715:
716: protected boolean isRevisionSpecified() {
717: return this .revision != -1;
718: }
719:
720: public InputStream getInputStream() {
721: try {
722: return getRepositoryNode().getInputStream();
723: } catch (RepositoryException e) {
724: throw new RuntimeException(e);
725: }
726: }
727:
728: public Session getSession() {
729: return getFactory().getSession();
730: }
731:
732: public int getRevisionNumber() {
733: if (!isRevisionSpecified()) {
734: throw new UnsupportedOperationException(
735: "This is not a particular revision of the document ["
736: + this + "].");
737: }
738: return this.revision;
739: }
740:
741: }
|