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.factory;
020:
021: import java.io.FileReader;
022: import java.util.*;
023: import java.util.logging.*;
024:
025: import javax.xml.transform.*;
026: import javax.xml.transform.dom.*;
027: import javax.xml.transform.stream.StreamSource;
028:
029: import org.openharmonise.commons.cache.CacheException;
030: import org.openharmonise.commons.dsi.*;
031: import org.openharmonise.commons.xml.*;
032: import org.openharmonise.rm.PopulateException;
033: import org.openharmonise.rm.commands.AbstractCmd;
034: import org.openharmonise.rm.config.ConfigSettings;
035: import org.openharmonise.rm.dsi.DataStoreInterfaceFactory;
036: import org.openharmonise.rm.metadata.Profile;
037: import org.openharmonise.rm.publishing.*;
038: import org.openharmonise.rm.resources.*;
039: import org.openharmonise.rm.resources.lifecycle.Editable;
040: import org.openharmonise.rm.resources.publishing.WebPage;
041: import org.w3c.dom.*;
042:
043: /**
044: * Factory class which provides a single point of access to instances of the
045: * core Harmonise objects, providing various methods for returning instances
046: * based on either class names or XML elements. This class uses the factory XSLT
047: * file to find class name and cache key information from XML element names. This file
048: * will contain templates such as the following:
049: *
050: * <code>
051: * <xsl:template match="User">
052: * <org.openharmonise.rm.resources.users.User cacheKeyType="id"/>
053: * </xsl:template>
054: * </code>
055: *
056: * @author Michael Bell
057: * @version $Revision: 1.5.2.1 $
058: *
059: */
060: public class HarmoniseObjectFactory {
061: //XML constants
062:
063: /**
064: * Attribute used to specify the cache key type in factory.xsl
065: */
066: public static final String ATTRIB_CACHE_KEY_TYPE = "cacheKeyType";
067:
068: /**
069: * Attribute used to specifiy the class name of the <code>ClassFinder</code>
070: * class for a given XML element.
071: */
072: public static final String ATTRIB_CLASS_FINDER = "classFinder";
073:
074: /**
075: * Configuration property name for the property which specifies the
076: * name of the factory XSLT file
077: */
078: private static String PNAME_CLASS_FACTORY_XSL = "CLASS_FACTORY_XSL";
079:
080: /**
081: * Constant value used for the cache key attribute to specify that
082: * the object does not get cached
083: */
084: public static final String NOT_APPLICABLE = "NA";
085:
086: /**
087: * The file path of the factory XSLT file
088: */
089: private static String factory_xsl_path = null;
090:
091: /**
092: * The factory XSLT <code>Templates</code> object
093: */
094: private static Templates factory_templates = null;
095:
096: /**
097: * Cache of element name to class name mappings
098: */
099: private static Map m_classname_cache = Collections
100: .synchronizedMap(new HashMap());
101:
102: /**
103: * Cache of element name to elements representing factory information.
104: */
105: private static Map m_element_cache = Collections
106: .synchronizedMap(new HashMap());
107:
108: /**
109: * Logger for this class
110: */
111: private static final Logger m_logger = Logger
112: .getLogger(HarmoniseObjectFactory.class.getName());
113:
114: private static Transformer factory_trans = null;
115:
116: //static initialiser black
117: static {
118: try {
119: factory_xsl_path = ConfigSettings
120: .getProperty(PNAME_CLASS_FACTORY_XSL);
121: StreamSource ssource = new StreamSource(new FileReader(
122: factory_xsl_path));
123:
124: factory_templates = org.apache.xalan.xsltc.trax.TransformerFactoryImpl
125: .newInstance().newTemplates(ssource);
126:
127: factory_trans = factory_templates.newTransformer();
128:
129: if (factory_xsl_path == null) {
130: throw new HarmoniseFactoryException(
131: "Error getting factory.xsl path");
132: }
133: } catch (Exception e) {
134: m_logger.log(Level.WARNING, e.getLocalizedMessage(), e);
135: }
136: }
137:
138: /**
139: * Returns an instance of <code>AbstractObject</code> of the specified class name and
140: * path using the given data store interface.
141: *
142: * @param dbinterf the data store interface
143: * @param sClassname the class name
144: * @param sPath the harmonise path of the desired object
145: * @return an instance of <code>AbstractObject</code> of the specified class name and
146: * path
147: * @throws HarmoniseFactoryException if any errors occur
148: */
149: public static AbstractObject instantiateHarmoniseObject(
150: AbstractDataStoreInterface dbinterf, String sClassname,
151: String sPath) throws HarmoniseFactoryException {
152: AbstractObject ohObj = null;
153:
154: try {
155: ohObj = (AbstractObject) instantiateObject(dbinterf,
156: sClassname, sPath);
157: } catch (ClassCastException e) {
158: throw new HarmoniseFactoryException(
159: "Object not an HarmoniseObject", e);
160: }
161:
162: return ohObj;
163: }
164:
165: /**
166: * Returns an instance of <code>Publishable<code> of the specified class name and
167: * path using the given data store interface.
168: *
169: * @param dbinterf the data store interface
170: * @param sClassname the class name
171: * @param sPath the harmonise path of the desired object
172: * @return an instance of <code>Publishable<code> of the specified class name and
173: * path
174: * @throws HarmoniseFactoryException if any errors occur
175: */
176: public static Publishable instantiatePublishableObject(
177: AbstractDataStoreInterface dbinterf, String sClassname,
178: String sPath) throws HarmoniseFactoryException {
179: Publishable pubObj = null;
180:
181: try {
182: pubObj = (Publishable) instantiateObject(dbinterf,
183: sClassname, sPath);
184: } catch (ClassCastException e) {
185: throw new HarmoniseFactoryException(
186: "Object not a PublishableObject", e);
187: }
188:
189: return pubObj;
190: }
191:
192: /**
193: * Returns an instance of <code>AbstractObject</code> of the specified class name and
194: * id using the given data store interface.
195: *
196: * @param dbinterf the data store interface
197: * @param sClassname the class name
198: * @param nId the object id
199: * @return an instance of <code>AbstractObject</code> of the specified class name and
200: * id
201: * @throws HarmoniseFactoryException if any errors occur
202: */
203: public static AbstractObject instantiateHarmoniseObject(
204: AbstractDataStoreInterface dbinterf, String sClassname,
205: int nId) throws HarmoniseFactoryException {
206: AbstractObject obj = null;
207:
208: try {
209: obj = (AbstractObject) instantiateObject(dbinterf,
210: sClassname, nId);
211:
212: //ensure object really exists if the id is greater than 0
213: if (nId > 0 && obj.exists() == false) {
214: obj = null;
215: }
216:
217: } catch (ClassCastException e) {
218: throw new HarmoniseFactoryException(
219: "Object not an HarmoniseObject", e);
220: }
221:
222: return obj;
223: }
224:
225: /**
226: * Returns an instance of <code>Publishable<code> of the specified class name and
227: * id using the given data store interface.
228: *
229: * @param dbinterf the data store interface
230: * @param sClassname the class name
231: * @param nId the object id
232: * @return an instance of <code>Publishable<code> of the specified class name and
233: * id
234: * @throws HarmoniseFactoryException if any errors occur
235: */
236: public static Publishable instantiatePublishableObject(
237: AbstractDataStoreInterface dbinterf, String sClassname,
238: int nId) throws HarmoniseFactoryException {
239: Publishable pubObj = null;
240:
241: try {
242: pubObj = (Publishable) instantiateObject(dbinterf,
243: sClassname, nId);
244: } catch (ClassCastException e) {
245: throw new HarmoniseFactoryException(
246: "Object not a PublishableObject", e);
247: }
248:
249: return pubObj;
250: }
251:
252: /**
253: * Returns an instance of <code>AbstractObject</code> which matches the
254: * the given XML element, taking any necessary additional information from the
255: * <code>State</code> object given.
256: *
257: * @param dbinterf the data store interface
258: * @param element the XML element representing the object to be instantiated
259: * @param state the state represenation
260: * @return an instance of <code>AbstractObject</code> which matches the
261: * the given XML element
262: * @throws HarmoniseFactoryException if any errors occur
263: */
264: public static AbstractObject instantiateHarmoniseObject(
265: AbstractDataStoreInterface dbinterf, Element element,
266: State state) throws HarmoniseFactoryException {
267: AbstractObject ohObj = null;
268:
269: try {
270: ohObj = (AbstractObject) instantiateObject(dbinterf,
271: element, state);
272: } catch (ClassCastException e) {
273: throw new HarmoniseFactoryException(
274: "Object not an HarmoniseObject", e);
275: }
276:
277: return ohObj;
278: }
279:
280: /**
281: * Returns an instance of <code>Publishable</code> which matches the
282: * the given XML element, taking any necessary additional information from the
283: * <code>State</code> object given.
284: *
285: * @param dbinterf the data store interface
286: * @param element the XML element representing the object to be instantiated
287: * @param state the state represenation
288: * @return an instance of <code>Publishable</code> which matches the
289: * the given XML element
290: * @throws HarmoniseFactoryException
291: */
292: public static Publishable instantiatePublishableObject(
293: AbstractDataStoreInterface dbinterf, Element element,
294: State state) throws HarmoniseFactoryException {
295: Publishable ohObj = null;
296:
297: ohObj = (Publishable) instantiateObject(dbinterf, element,
298: state);
299:
300: return ohObj;
301: }
302:
303: /**
304: * Returns a list of objects from the WorkflowObject XML element.
305: *
306: * @param dbinterf the data store interface
307: * @param wf_root the WorkflowObject XML element
308: * @param state the state representation
309: * @return a list of objects
310: * @throws HarmoniseFactoryException if any errors occur
311: */
312: public static List instantiateWorkflowObjects(
313: AbstractDataStoreInterface dbinterf, Element wf_root,
314: State state) throws HarmoniseFactoryException {
315: Vector wrkflwObjs = new Vector(16);
316:
317: Element state_profile = null;
318:
319: NodeList profile_nodes = state
320: .getElementsByTagName(Profile.TAG_PROFILE);
321:
322: for (int i = 0; i < profile_nodes.getLength(); i++) {
323: if (((Element) profile_nodes.item(i)).getAttribute(
324: WebPage.ATTRIB_NAME).equalsIgnoreCase("Profile")
325: || ((Element) profile_nodes.item(i)).getAttribute(
326: WebPage.ATTRIB_NAME).equalsIgnoreCase(
327: "Default")) {
328: state_profile = (Element) profile_nodes.item(i);
329: }
330: }
331:
332: NodeList wf_nodes = wf_root.getChildNodes();
333:
334: for (int i = 0; i < wf_nodes.getLength(); i++) {
335: // ignore non-element nodes
336: if (wf_nodes.item(i).getNodeType() != Node.ELEMENT_NODE) {
337: continue;
338: }
339:
340: Element workflow_element = (Element) wf_nodes.item(i);
341:
342: //skip Event tags
343: if (workflow_element.getTagName().equalsIgnoreCase(
344: AbstractCmd.TAG_COMMAND)) {
345: continue;
346: }
347: List vObjElements = state.findElements(workflow_element);
348:
349: if (vObjElements.size() == 0) {
350: vObjElements.add(workflow_element);
351: }
352:
353: Editable wrkflwObj = null;
354:
355: for (int j = 0; j < vObjElements.size(); j++) {
356: Element elTemp = (Element) vObjElements.get(j);
357:
358: wrkflwObj = (Editable) instantiateObject(dbinterf,
359: elTemp, state);
360:
361: if (wrkflwObj != null) {
362: try {
363: ((Publishable) wrkflwObj).populate(elTemp,
364: state);
365: } catch (PopulateException e) {
366: throw new HarmoniseFactoryException(
367: "Populate error", e);
368: }
369:
370: wrkflwObjs.addElement(wrkflwObj);
371: }
372: }
373: }
374:
375: if (m_logger.isLoggable(Level.FINE)) {
376: m_logger.logp(Level.FINE, HarmoniseObjectFactory.class
377: .getName(), "instantiateWorkflowObjects",
378: "Got following WorkflowObjects from cache: "
379: + wrkflwObjs);
380: }
381:
382: return wrkflwObjs;
383: }
384:
385: /*-----------------------------------------------------------------------------
386: Private methods
387: ------------------------------------------------------------------------------*/
388:
389: /**
390: * Returns an instance of an object of the given class name and id using
391: * the given data store interface.
392: *
393: * @param dbinterf the data store interface
394: * @param sClassname the class name
395: * @param nId the object id
396: * @return an instance of an object of the given class name and id
397: * @throws HarmoniseFactoryException if any errors occur
398: */
399: private static Object instantiateObject(
400: AbstractDataStoreInterface dbinterf, String sClassname,
401: int nId) throws HarmoniseFactoryException {
402: Object obj;
403: try {
404: obj = (Object) CacheHandler.getInstance(dbinterf)
405: .getObject(sClassname, nId);
406: } catch (CacheException e) {
407: throw new HarmoniseFactoryException(
408: "Error getting object from cache", e);
409: }
410:
411: if (m_logger.isLoggable(Level.FINER)) {
412: m_logger.logp(Level.FINER, HarmoniseObjectFactory.class
413: .getName(), "insantiateObject", "instantiating "
414: + sClassname + " with id " + nId);
415: }
416:
417: return obj;
418: }
419:
420: /**
421: * Returns an instance of an object of the given class name and path using
422: * the given data store interface.
423: *
424: * @param dbinterf the data store interface
425: * @param sClassname the class name
426: * @param sPath the harmonise path of the desired object
427: * @return an instance of an object of the given class name and path
428: * @throws HarmoniseFactoryException if any errors occur
429: */
430: private static Object instantiateObject(
431: AbstractDataStoreInterface dbinterf, String sClassname,
432: String sPath) throws HarmoniseFactoryException {
433: AbstractObject ohObj;
434: try {
435: ohObj = (AbstractObject) CacheHandler.getInstance(dbinterf)
436: .getObjectFromPath(sClassname, sPath);
437: } catch (CacheException e) {
438: throw new HarmoniseFactoryException(
439: "Error getting object from cache", e);
440: }
441:
442: if (m_logger.isLoggable(Level.FINER)) {
443: m_logger.logp(Level.FINER, HarmoniseObjectFactory.class
444: .getName(), "insantiateObject", "instantiating "
445: + sClassname + " with path " + sPath);
446: }
447:
448: return ohObj;
449: }
450:
451: /**
452: * Returns an instance of an object which matches the
453: * the given XML element, taking any necessary additional information from the
454: * <code>State</code> object given.
455: *
456: * @param dbinterf the data store interface
457: * @param element the XML element representing the object to be instantiated
458: * @param state the state representation
459: * @return an instance of an object which matches the
460: * the given XML element
461: * @throws HarmoniseFactoryException if any errors occur
462: */
463: private static Object instantiateObject(
464: AbstractDataStoreInterface dbinterf, Element element,
465: State state) throws HarmoniseFactoryException {
466: Object Obj = null;
467: boolean bUsingPath = false;
468:
469: Element classElement = getClassElement(dbinterf, element);
470:
471: String sClassName = classElement.getTagName();
472:
473: if (m_logger.isLoggable(Level.FINE)) {
474: m_logger.logp(Level.FINE, HarmoniseObjectFactory.class
475: .getName(), "instantiateObject",
476: "Instantiating object from XML: element - "
477: + sClassName + " and state");
478: }
479:
480: String sCacheKeyType = classElement
481: .getAttribute(ATTRIB_CACHE_KEY_TYPE);
482: String sClassFinder = classElement
483: .getAttribute(ATTRIB_CLASS_FINDER);
484:
485: if (sCacheKeyType.equalsIgnoreCase(NOT_APPLICABLE) == true) {
486: try {
487: Obj = GeneralCache.createObject(dbinterf, sClassName);
488: } catch (CacheException e) {
489: throw new HarmoniseFactoryException(
490: "Error getting object from cache", e);
491: }
492: } else {
493: String sCacheKey = element.getAttribute(sCacheKeyType);
494:
495: // if we didn't find a key, first try the path, then if we don't have that
496: // get the first one off the state
497: if ((sCacheKey == null) || (sCacheKey.length() == 0)) {
498: String sPath = findObjectPath(element, state);
499:
500: if ((sPath != null) && (sPath.length() > 0)) {
501: sCacheKey = sPath;
502: bUsingPath = true;
503: }
504:
505: if ((bUsingPath == false) && (state != null)) {
506: Element el = state.findElement(element);
507:
508: if (el != null) {
509: sCacheKey = el.getAttribute(sCacheKeyType);
510:
511: if ((sCacheKey == null)
512: || (sCacheKey.length() == 0)) {
513: sCacheKey = findObjectPath(el, state);
514: bUsingPath = true;
515: }
516: }
517: }
518: }
519:
520: // ids are ints, everything other type of key is a String
521: if (bUsingPath) {
522: try {
523: Obj = (Object) CacheHandler.getInstance(dbinterf)
524: .getObjectFromPath(sClassName, sCacheKey);
525: } catch (CacheException e) {
526: throw new HarmoniseFactoryException(
527: "Error getting object from cache", e);
528: }
529: } else if (sCacheKeyType.equals(AbstractObject.ATTRIB_ID)) {
530: int nId = -1;
531:
532: if (sCacheKey != null) {
533: try {
534: nId = Integer.parseInt(sCacheKey);
535: } catch (NumberFormatException e) {
536: nId = -1;
537: }
538: }
539:
540: if ((sClassFinder != null)
541: && (sClassFinder.length() > 0)) {
542: try {
543: ClassFinder finder = ClassFinderFactory
544: .getInstance(dbinterf).getClassFinder(
545: sClassFinder);
546: sClassName = finder.getClass(nId);
547: } catch (ClassFinderException e) {
548: throw new HarmoniseFactoryException(
549: "Exception", e);
550: }
551: }
552:
553: try {
554: Obj = (Object) CacheHandler.getInstance(dbinterf)
555: .getObject(sClassName, nId);
556: } catch (CacheException e) {
557: throw new HarmoniseFactoryException(
558: "Error getting object from cache", e);
559: }
560: } else {
561: try {
562: Obj = (Object) CacheHandler.getInstance(dbinterf)
563: .getObject(sClassName, sCacheKey);
564: } catch (CacheException e) {
565: throw new HarmoniseFactoryException(
566: "Error getting object from cache", e);
567: }
568: }
569: }
570:
571: if (m_logger.isLoggable(Level.FINER)) {
572: m_logger.logp(Level.FINER, HarmoniseObjectFactory.class
573: .getName(), "instantiateObject",
574: "Got following Object from cache: " + Obj);
575: }
576:
577: return Obj;
578: }
579:
580: /**
581: * Returns the class name associated to the given XML element, taken
582: * from the factory XSLT.
583: *
584: * @param dbintrf the data store interface
585: * @param element the XML element
586: * @return the class name associated to the given XML element
587: * @throws HarmoniseFactoryException if any errors occur
588: */
589: public static String getClassName(
590: AbstractDataStoreInterface dbintrf, Element element)
591: throws HarmoniseFactoryException {
592: String sTagName = XMLUtils.getElementName(element);
593: String sClassname = (String) m_classname_cache.get(sTagName);
594:
595: //double checked synchonized block
596: if (sClassname == null) {
597: synchronized (m_classname_cache) {
598: sClassname = (String) m_classname_cache.get(sTagName);
599: if (sClassname == null) {
600: sClassname = getClassElement(dbintrf, element)
601: .getTagName();
602:
603: m_classname_cache.put(sTagName, sClassname);
604: }
605: }
606: }
607:
608: return sClassname;
609: }
610:
611: /**
612: * Returns the XML element from the factory XSLT whose tag name is
613: * the class name associated to the given XML element.
614: *
615: * @param dbintrf the data store interface
616: * @param element the XML element
617: * @return the XML element from the factory XSLT whose tag name is
618: * the class name associated to the given XML element
619: * @throws HarmoniseFactoryException if any errors occur
620: */
621: private static Element getClassElement(
622: AbstractDataStoreInterface dbintrf, Element element)
623: throws HarmoniseFactoryException {
624:
625: String elementName = element.getTagName();
626: Element classElement = (Element) m_element_cache
627: .get(elementName);
628: if (classElement == null) {
629: try {
630:
631: XMLDocument inputDoc = new XMLDocument();
632: Element classNameEl = inputDoc.createElement(XMLUtils
633: .getElementName(element));
634: inputDoc.appendChild(classNameEl);
635:
636: XMLDocument outputDoc = new XMLDocument();
637:
638: DOMSource ds = new DOMSource(inputDoc
639: .getDocumentElement());
640:
641: DOMResult res = new DOMResult();
642:
643: res.setNode(outputDoc);
644:
645: factory_trans.transform(ds, res);
646:
647: classElement = outputDoc.getDocumentElement();
648:
649: // cache the classElement
650: m_element_cache.put(elementName, classElement);
651:
652: } catch (Exception e) {
653: throw new HarmoniseFactoryException(e);
654: }
655:
656: }
657:
658: if (classElement == null) {
659: throw new HarmoniseFactoryException(
660: "No class found for element "
661: + element.getTagName());
662: }
663:
664: return classElement;
665: }
666:
667: /**
668: * Returns the absolute harmonise path from the given XML element which represents
669: * a harmonise object, taking context from the given <code>State</code> to
670: * resolve any relative path references found in the 'Path' child element
671: * of the given element.
672: *
673: * @param element the object XML element
674: * @param state the state context
675: * @return the absolute harmonise path from the given XML element
676: * @throws HarmoniseFactoryException if any errors occur
677: */
678: private static String findObjectPath(Element element, State state)
679: throws HarmoniseFactoryException {
680: String sPath = null;
681: NodeList nodes = element.getChildNodes();
682:
683: for (int i = 0; i < nodes.getLength(); i++) {
684: if (nodes.item(i).getNodeType() != Node.ELEMENT_NODE) {
685: continue;
686: }
687:
688: Element el = (Element) nodes.item(i);
689:
690: if (el.getTagName().equals(AbstractChildObject.TAG_PATH)) {
691: Text txt = (Text) el.getFirstChild();
692:
693: if (txt != null) {
694: sPath = txt.getNodeValue().trim();
695:
696: // resolve relative paths, if necessary
697: if ((state != null) && sPath.startsWith(".")) {
698: try {
699: sPath = state.resolveRelativePath(element
700: .getTagName(), sPath);
701: } catch (Exception e) {
702: throw new HarmoniseFactoryException(
703: "Error resolving relative path: "
704: + sPath + " "
705: + e.getLocalizedMessage());
706: }
707: }
708: }
709:
710: break;
711: }
712: }
713:
714: return sPath;
715: }
716:
717: /**
718: * @param xmlElement
719: * @return
720: */
721: public static String getClassName(Element xmlElement)
722: throws HarmoniseFactoryException {
723:
724: AbstractDataStoreInterface dsi = null;
725:
726: try {
727: dsi = DataStoreInterfaceFactory.getDataStoreInterface();
728: } catch (DataStoreException e) {
729: throw new HarmoniseFactoryException(e);
730: }
731:
732: return getClassName(dsi, xmlElement);
733: }
734: }
|