001: /*
002: * The contents of this file are subject to the
003: * Mozilla Public License Version 1.1 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
006: *
007: * Software distributed under the License is distributed on an "AS IS"
008: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
009: * See the License for the specific language governing rights and
010: * limitations under the License.
011: *
012: * The Initial Developer of the Original Code is Simulacra Media Ltd.
013: * Portions created by Simulacra Media Ltd are Copyright (C) Simulacra Media Ltd, 2004.
014: *
015: * All Rights Reserved.
016: *
017: * Contributor(s):
018: */
019: package org.openharmonise.rm.resources.content;
020:
021: import java.util.Vector;
022: import java.util.logging.*;
023:
024: import org.openharmonise.commons.dsi.AbstractDataStoreInterface;
025: import org.openharmonise.commons.xml.*;
026: import org.openharmonise.commons.xml.namespace.NamespaceType;
027: import org.openharmonise.rm.*;
028: import org.openharmonise.rm.dsi.*;
029: import org.openharmonise.rm.factory.*;
030: import org.openharmonise.rm.metadata.Profile;
031: import org.openharmonise.rm.publishing.*;
032: import org.openharmonise.rm.resources.lifecycle.Editable;
033: import org.openharmonise.rm.resources.publishing.*;
034: import org.openharmonise.rm.resources.xml.XMLResource;
035: import org.w3c.dom.*;
036:
037: /**
038: * The <code>Document</code> class represents a content XML document resource
039: * which generally conform the Harmonise document XML schema.
040: *
041: * @author Michael Bell
042: * @version $Revision: 1.7 $
043: *
044: */
045: public class Document extends XMLResource implements DataStoreObject,
046: Publishable, Editable, Cloneable, Comparable {
047:
048: //DB constants
049: /**
050: * The <code>Document</code> database table name
051: */
052: private static final String TBL_DOCUMENT = "document";
053:
054: //XML constants
055: /**
056: * Selections and Index XML attribute name found in the document schema
057: */
058: public static final String ATTRIB_SELECTIONS_AND_INDEX = "selectionsAndIndex";
059: /**
060: * The <code>Document</code> XML tag name
061: */
062: public static final String TAG_DOCUMENT = "Document";
063:
064: /**
065: * The flag index
066: */
067: public static final String FLAG_INDEX = "index";
068:
069: /**
070: * The chapter tag name
071: */
072: public static final String TAG_CHAPTER = "Chapter";
073:
074: private static final Logger m_logger = Logger
075: .getLogger(Document.class.getName());
076:
077: //static initialiser block
078: static {
079: DatabaseInfo.getInstance().registerTableName(
080: Document.class.getName(), TBL_DOCUMENT);
081: }
082:
083: /**
084: * Constructs an instance with no data store interface and no other details.
085: *
086: */
087: public Document() {
088: super ();
089:
090: }
091:
092: /**
093: * Standard constructor for a new or anonymous resource.
094: *
095: * @param dbintrf the data store to register
096: */
097: public Document(AbstractDataStoreInterface dbintrf) {
098: super (dbintrf);
099:
100: }
101:
102: /**
103: * Standard constructor for a known resource.
104: *
105: * @param dbintrf the data store to register
106: * @param nId the id of this resource
107: */
108: public Document(AbstractDataStoreInterface dbintrf, int nId) {
109: super (dbintrf, nId);
110: }
111:
112: /**
113: * Standard constructor for a known resource which may be historical.
114: *
115: * @param dbintrf the data store interface
116: * @param nId the resource identifier
117: * @param nKey the unique resource key
118: * @param bIsHist <code>true</code> if the resoure is historical, otherwise <code>false</code>
119: */
120: public Document(AbstractDataStoreInterface dbintrf, int nId,
121: int nKey, boolean bIsHist) {
122: super (dbintrf, nId, nKey, bIsHist);
123:
124: }
125:
126: /** Set method for the <code>Document</code>'s contents.
127: *
128: * @param sContent the contents to set
129: */
130: public void setContent(String sContent) throws PopulateException {
131: super .setContent(sContent);
132:
133: try {
134: XMLDocument doc = getDocument();
135:
136: if (doc.getDocumentElement().getLocalName().equals(
137: TAG_CONTENT) == false) {
138: String sMainContent = sContent;
139:
140: if (sContent.startsWith("<?xml") == true) {
141: int nIndex = sContent.indexOf("?>");
142: if (nIndex > 0) {
143: sMainContent = sContent.substring(nIndex + 2);
144: }
145: }
146:
147: StringBuffer sBuf = new StringBuffer();
148:
149: sBuf.append("<").append(TAG_CONTENT).append(">");
150: sBuf.append(sMainContent);
151: sBuf.append("</").append(TAG_CONTENT).append(">");
152:
153: super .setContent(sBuf.toString());
154: }
155: } catch (DataAccessException e) {
156: throw new PopulateException(e);
157: }
158:
159: }
160:
161: /* (non-Javadoc)
162: * @see java.lang.Object#toString()
163: */
164: public String toString() {
165: StringBuffer strBuff = new StringBuffer();
166:
167: strBuff.append("Document Title:[" + m_sName + "] ").append(
168: "Document Summary:[" + m_sSummary + "] ").append(
169: "Document ID:[" + m_nId + "] ");
170:
171: try {
172: Profile prof = getProfile();
173:
174: if (prof != null) {
175: strBuff.append("Document profile:[" + prof.toString()
176: + "]");
177: }
178:
179: } catch (DataAccessException e) {
180: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
181: }
182:
183: return strBuff.toString();
184: }
185:
186: /* (non-Javadoc)
187: * @see org.openharmonise.rm.publishing.Publishable#publish(org.w3c.dom.Element, org.openharmonise.rm.publishing.HarmoniseOutput, org.openharmonise.rm.publishing.State)
188: */
189: public Element publish(Element topEl, HarmoniseOutput xmlDoc,
190: State state) throws PublishException {
191:
192: Element docEl = null;
193: NodeList nodes = null;
194: Text txt = null;
195: String sTagName = XMLUtils.getElementName(topEl);
196:
197: if (sTagName.equals(TAG_CONTENT)) {
198: org.w3c.dom.Document txtDoc;
199: try {
200: txtDoc = getDocument();
201: } catch (DataAccessException e) {
202: throw new PublishException(e);
203: }
204:
205: if (txtDoc != null) {
206:
207: Element docRoot = txtDoc.getDocumentElement();
208:
209: if (docRoot == null) {
210: throw new PublishException(
211: "Parsed document contents return as null :"
212: + " for document id " + m_nId
213: + " contents:" + m_sContent);
214: }
215:
216: docEl = publishContents(docRoot, topEl, xmlDoc, state);
217: } else {
218: docEl = (Element) xmlDoc.copyNode(topEl);
219: }
220: } else {
221: // if we don't know about the tag, pass it up
222: docEl = super .publish(topEl, xmlDoc, state);
223: }
224:
225: return docEl;
226: }
227:
228: /**
229: * Populates the object using data conatined in XML element
230: * and with the specified text for this <code>Document</code>'s
231: * content.
232: *
233: * @param xmlElement the XML element
234: * @param sDocContent the document contents
235: * @throws PopulateException
236: */
237: public void populate(Element xmlElement, String sDocContent)
238: throws PopulateException {
239:
240: try {
241: populate(xmlElement, new State(this .m_dsi));
242: } catch (StateException e) {
243: throw new PopulateException("Error creating new State", e);
244: }
245:
246: m_sContent = sDocContent;
247: }
248:
249: /* (non-Javadoc)
250: * @see org.openharmonise.rm.dsi.DataStoreObject#getDBTableName()
251: */
252: public String getDBTableName() {
253:
254: return TBL_DOCUMENT;
255: }
256:
257: /* (non-Javadoc)
258: * @see org.openharmonise.rm.resources.AbstractChildObject#getParentObjectClassName()
259: */
260: public String getParentObjectClassName() {
261: return Section.class.getName();
262: }
263:
264: /* (non-Javadoc)
265: * @see org.openharmonise.rm.publishing.Publishable#getTagName()
266: */
267: public String getTagName() {
268: return TAG_DOCUMENT;
269: }
270:
271: /*----------------------------------------------------------------------------
272: Private methods
273: -----------------------------------------------------------------------------*/
274:
275: /**
276: * Publishes the contents of a document to XML.
277: *
278: * @param contentsEl the element containing the full contents
279: * @param templateEl the element containing any templates to be published to
280: * @param xmlDoc the owner XML <code>Document</code>
281: * @param state the context/state for this operation
282: * @return the resultant XML element
283: * @throws PublishException if any errors occur
284: */
285: private Element publishContents(Element contentsEl,
286: Element templateEl, HarmoniseOutput xmlDoc, State state)
287: throws PublishException {
288:
289: String sTagName = XMLUtils.getElementName(contentsEl);
290:
291: if (sTagName.equals(TAG_CONTENT) == false) {
292: throw new InvalidXMLElementException(TAG_CONTENT
293: + " tag needed");
294: }
295:
296: /* There are three cases:
297: 1. Publish the entire contents
298: 2. Publish the parts of the contents identified by the templateEl and the state
299: 3. Publish the parts of the contents identified by the state in full and the rest
300: using a Template that should be contained in templateEl
301: */
302: Element elReturn = null;
303: String sFlag = templateEl
304: .getAttribute(ATTRIB_SELECTIONS_AND_INDEX);
305:
306: if ((sFlag != null) && (sFlag.length() > 0)) {
307: // this is case 3
308: elReturn = publishSelectionsAndIndex(contentsEl,
309: templateEl, xmlDoc, state, sFlag);
310: } else {
311: NodeList nlTest = templateEl.getChildNodes();
312:
313: if (nlTest.getLength() > 0) {
314: // this is case 2
315: elReturn = publishSelectionsOnly(contentsEl,
316: templateEl, xmlDoc, state);
317: } else {
318: // this case 1
319: elReturn = publishFullContents(contentsEl, xmlDoc,
320: state);
321: }
322: }
323:
324: return elReturn;
325: }
326:
327: /**
328: * Publishes elements under the contents that are in the state in full,
329: * and others using a template.
330: *
331: * @param contentsEl the document contents
332: * @param templateEl the template
333: * @param xmlDoc the owner document
334: * @param state the context/state for this operation
335: * @return the resultant XML element
336: * @throws PublishException if any errors occur
337: */
338: private Element publishSelectionsAndIndex(Element contentsEl,
339: Element templateEl, HarmoniseOutput xmlDoc, State state,
340: String sFlag) throws PublishException {
341: /* This is the only version that doesn't recurse
342: The elements that can be published as an index have to appear at the top level &
343: have to be called 'Chapter'
344: */
345: Element elReturn = null;
346: Element elStateDoc = state.findElement(state
347: .createElement(TAG_DOCUMENT));
348: Vector chapters = new Vector();
349: NodeList nodeChapters = null;
350:
351: // the state should contain a list of all the chapters that are to appear in full
352: if (sFlag.equals(FLAG_INDEX) == false) {
353: boolean bFound = false;
354:
355: if (elStateDoc != null) {
356: String sId = elStateDoc.getAttribute(ATTRIB_ID);
357:
358: if (sId != null) {
359: if (m_nId == Integer.parseInt(sId)) {
360: bFound = true;
361: }
362: } else {
363: Element elPath = XMLUtils.getFirstNamedChild(
364: elStateDoc, TAG_PATH);
365:
366: if (elPath != null) {
367: String sPath = elPath.getFirstChild()
368: .getNodeValue();
369:
370: try {
371: if (sPath.equals(getPath() + "/"
372: + getName())) {
373: bFound = true;
374: }
375: } catch (DataAccessException e) {
376: throw new PublishException(
377: "Error occured getting name or path",
378: e);
379: }
380: }
381: }
382:
383: if (bFound) {
384: nodeChapters = elStateDoc
385: .getElementsByTagName(TAG_CHAPTER);
386:
387: for (int i = 0; i < nodeChapters.getLength(); i++) {
388: Element el = (Element) nodeChapters.item(i);
389: chapters.add(el.getAttribute(ATTRIB_ID));
390: }
391: }
392: }
393:
394: if (chapters.size() == 0) {
395: nodeChapters = contentsEl.getElementsByTagNameNS(
396: NamespaceType.OHRM_DOC.getURI(), TAG_CHAPTER);
397:
398: if (nodeChapters.getLength() > 0) {
399: Element el = (Element) nodeChapters.item(0);
400: chapters.add(el.getAttribute(ATTRIB_ID));
401: }
402:
403: }
404: }
405:
406: elReturn = xmlDoc.createElement(TAG_CONTENT);
407:
408: NodeList nodes = contentsEl.getChildNodes();
409: Element elLinkTemplate = (Element) templateEl
410: .getElementsByTagName(Template.TAG_TEMPLATE).item(0);
411:
412: Template template = null;
413:
414: try {
415: template = (Template) HarmoniseObjectFactory
416: .instantiateHarmoniseObject(m_dsi, Template.class
417: .getName(), Integer.parseInt(elLinkTemplate
418: .getAttribute(ATTRIB_ID)));
419: } catch (NumberFormatException e) {
420: throw new PublishException(e);
421: } catch (HarmoniseFactoryException e) {
422: throw new PublishException(e);
423: }
424:
425: int nPageId = 0;
426: NodeList nodesPage = elLinkTemplate
427: .getElementsByTagName(WebPage.TAG_PAGE);
428:
429: if (nodesPage.getLength() > 0) {
430: Element elPage = (Element) nodesPage.item(0);
431: nPageId = Integer.parseInt(elPage.getAttribute(ATTRIB_ID));
432: }
433:
434: // loop through the top level elements in the contents
435: for (int i = 0; i < nodes.getLength(); i++) {
436: if (nodes.item(i).getNodeType() != Node.ELEMENT_NODE) {
437: continue;
438: }
439:
440: Element el = (Element) nodes.item(i);
441:
442: if (XMLUtils.getElementName(el).equals(TAG_CHAPTER)) {
443: String sId = el.getAttribute(ATTRIB_ID);
444:
445: if (chapters.contains(sId)) {
446: // a chapter that is in the state, publish it in full
447: elReturn.appendChild(publishFullContents(el,
448: xmlDoc, state));
449: } else {
450: // a chapter that isn't in the state, publish with the supplied template
451: // first create a new state that contains a reference to this chapter and no other
452: try {
453: State stateNew = null;
454:
455: stateNew = new State(
456: (org.w3c.dom.Document) state, m_dsi);
457:
458: Element elNewStateDoc = stateNew
459: .findElement(state
460: .createElement(TAG_DOCUMENT));
461:
462: if (elNewStateDoc != null) {
463: NodeList children = elNewStateDoc
464: .getChildNodes();
465:
466: for (int j = 0; j < children.getLength(); j++) {
467: elNewStateDoc.removeChild(children
468: .item(j));
469: }
470: } else {
471: elNewStateDoc = stateNew
472: .createElement(TAG_DOCUMENT);
473: elNewStateDoc.setAttribute(ATTRIB_ID,
474: Integer.toString(getId()));
475: stateNew.getDocumentElement().appendChild(
476: elNewStateDoc);
477: }
478:
479: Element elThisChapter = stateNew
480: .createElement(TAG_CHAPTER);
481: elThisChapter.setAttribute(ATTRIB_ID, sId);
482: elNewStateDoc.appendChild(elThisChapter);
483:
484: // publish the whole document with the template
485: Element elPublish = template
486: .publishObjectToElement(this , xmlDoc,
487: stateNew);
488:
489: if (nPageId != 0) {
490: xmlDoc.addPageIdToLinkNode(state
491: .getLoggedInUser(), elPublish,
492: nPageId);
493: }
494:
495: elReturn.appendChild(elPublish);
496: } catch (StateException e) {
497: throw new PublishException(
498: "Error creating new State", e);
499: }
500: }
501: } else {
502: // a non-chapter element, publish in full
503: elReturn.appendChild(publishFullContents(el, xmlDoc,
504: state));
505: }
506: }
507:
508: return elReturn;
509: }
510:
511: /**
512: * Publishes only the parts of an element in the contents that are
513: * present in a template.
514: *
515: * @param contentsEl the element to be published
516: * @param templateEl the template to use
517: * @param xmlDoc the output document
518: * @param state the context/state for this operation
519: * @return the published element
520: * @throws PublishException if any errors occur
521: */
522: private Element publishSelectionsOnly(Element contentsEl,
523: Element templateEl, HarmoniseOutput xmlDoc, State state)
524: throws PublishException {
525: // recurses, so can be called at any level within the contents
526: NodeList nodesTemplate = templateEl.getChildNodes();
527: Element elStateDoc = state.findElement(state
528: .createElement(TAG_DOCUMENT));
529:
530: // shallow copy the element into the output, the fact that it has got this far means
531: // it should be published, the rest of the function decides which of its children
532: // deserve the same...
533: Element elReturn = null;
534:
535: String sTagname = XMLUtils.getElementName(contentsEl);
536:
537: if (sTagname.equals(TAG_CONTENT)) {
538: elReturn = xmlDoc.createElement(TAG_CONTENT);
539: } else {
540: elReturn = (Element) xmlDoc.importNode(contentsEl
541: .cloneNode(false), false);
542: }
543:
544: boolean bFound = false;
545:
546: // loop through the children of the template element to see which of the current elements
547: // children should be published
548: for (int i = 0; i < nodesTemplate.getLength(); i++) {
549: if (nodesTemplate.item(i).getNodeType() != Node.ELEMENT_NODE) {
550: continue;
551: }
552:
553: bFound = true;
554:
555: Element elNext = (Element) nodesTemplate.item(i);
556: String sTagName = XMLUtils.getElementName(elNext);
557:
558: // get all of the children in the contents that match the current child of the template
559: NodeList nodesContents = contentsEl.getElementsByTagNameNS(
560: NamespaceType.OHRM_DOC.getURI(), sTagName);
561:
562: // see if an id is specified
563: String sId = elNext.getAttribute(ATTRIB_ID);
564:
565: // if not, see if there are any ids on the state for elements of this type
566: if ((sId == null || sId.length() == 0)
567: && (elStateDoc != null)) {
568: NodeList nodes = elStateDoc
569: .getElementsByTagName(sTagName);
570:
571: if (nodes.getLength() > 0) {
572: sId = ((Element) nodes.item(0))
573: .getAttribute(ATTRIB_ID);
574: }
575: }
576:
577: // loop through the children of the correct type, deciding which ones to publish
578: for (int j = 0; j < nodesContents.getLength(); j++) {
579: Element el = (Element) nodesContents.item(j);
580: String sNextId = null;
581:
582: if ((sId != null) && (sId.length() > 0)) {
583: sNextId = el.getAttribute(ATTRIB_ID);
584: }
585:
586: // we publish if:
587: // 1. there is no id specified, i.e. publish all
588: // 2. the ids match
589: if ((sId == null || sId.length() == 0)
590: || (sNextId != null && sNextId.equals(sId))) {
591: elReturn.appendChild(publishSelectionsOnly(el,
592: elNext, xmlDoc, state));
593: }
594: }
595: }
596:
597: // this flag remains unset if the template element had no children
598: // this is taken to mean 'publish all children'
599: if (bFound == false) {
600: xmlDoc.copyChildren(elReturn, contentsEl);
601: }
602:
603: return elReturn;
604: }
605:
606: /**
607: * Publishes the whole of a XML tag within the <code>Document</code> contents.
608: *
609: * @param contentEl the element within the contents to publish
610: * @param xmlDoc the output documemt
611: * @param state the context/state for this operation
612: * @return the resultant XML document
613: * @throws PublishException if any errors occur
614: */
615: private Element publishFullContents(Element contentEl,
616: HarmoniseOutput xmlDoc, State state)
617: throws PublishException {
618: // recurses, so can be called at any level in the contents XML
619: Element returnEl = null;
620:
621: NodeList nlContents = null;
622: String sTagName;
623: int nTemplateId;
624: Template template = null;
625:
626: sTagName = XMLUtils.getElementName(contentEl);
627:
628: // process templates by publishing the correct object into the contents
629: if (sTagName.equals(Template.TAG_TEMPLATE)) {
630: nTemplateId = Integer.parseInt(contentEl
631: .getAttribute(ATTRIB_ID));
632: Element templRoot = null;
633:
634: try {
635: template = (Template) HarmoniseObjectFactory
636: .instantiateHarmoniseObject(m_dsi,
637: Template.class.getName(), nTemplateId);
638:
639: templRoot = template.getTemplateRootElement();
640: } catch (DataAccessException e) {
641: throw new PublishException(
642: "Error occured getting root element of template",
643: e);
644: } catch (HarmoniseFactoryException e) {
645: throw new PublishException(
646: "Error occured getting root element of template",
647: e);
648: }
649:
650: NodeList children = contentEl.getChildNodes();
651:
652: for (int j = 0; j < children.getLength(); j++) {
653: if (children.item(j).getNodeType() == Node.ELEMENT_NODE) {
654: Element child = (Element) children.item(j);
655:
656: if (XMLUtils.getElementName(templRoot)
657: .equalsIgnoreCase(
658: XMLUtils.getElementName(child))) {
659:
660: try {
661: Publishable pubObj = HarmoniseObjectFactory
662: .instantiatePublishableObject(
663: m_dsi, child, state);
664:
665: if (pubObj != null) {
666: returnEl = template
667: .publishObjectToElement(pubObj,
668: xmlDoc, state);
669: }
670: } catch (HarmoniseFactoryException e) {
671: throw new PublishException(
672: "Error occured getting object from factory",
673: e);
674: }
675:
676: }
677: }
678: }
679: } else {
680: // all other tags are copied accross & then their children are dealt with below
681:
682: if (sTagName.equals(TAG_CONTENT)) {
683: returnEl = xmlDoc.createElement(TAG_CONTENT);
684: } else {
685: returnEl = (Element) xmlDoc.importNode(contentEl
686: .cloneNode(false), false);
687: }
688:
689: nlContents = contentEl.getChildNodes();
690: }
691:
692: if (nlContents != null) {
693: for (int i = 0; i < nlContents.getLength(); i++) {
694: // copy text nodes straight accross, this is the end condition for the recursion
695: if (nlContents.item(i).getNodeType() == Node.TEXT_NODE) {
696: returnEl.appendChild(xmlDoc
697: .createTextNode(nlContents.item(i)
698: .getNodeValue()));
699: }
700: // recurse for all child elements
701: else if (nlContents.item(i).getNodeType() == Node.ELEMENT_NODE) {
702: contentEl = (Element) nlContents.item(i);
703: Element publishEl = publishFullContents(contentEl,
704: xmlDoc, state);
705:
706: if (publishEl != null) {
707: returnEl.appendChild(publishEl);
708: }
709: }
710: }
711: }
712:
713: return returnEl;
714: }
715:
716: }
|