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: package org.apache.jetspeed.page.document.psml;
018:
019: import java.io.File;
020: import java.io.FileInputStream;
021: import java.io.FileNotFoundException;
022: import java.io.FileOutputStream;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.io.InputStreamReader;
026: import java.io.OutputStreamWriter;
027: import java.io.Writer;
028:
029: import javax.xml.parsers.ParserConfigurationException;
030: import javax.xml.parsers.SAXParser;
031: import javax.xml.parsers.SAXParserFactory;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035: import org.apache.jetspeed.cache.file.FileCache;
036: import org.apache.jetspeed.cache.file.FileCacheEntry;
037: import org.apache.jetspeed.cache.file.FileCacheEventListener;
038: import org.apache.jetspeed.om.common.SecurityConstraints;
039: import org.apache.jetspeed.om.folder.psml.FolderImpl;
040: import org.apache.jetspeed.om.page.Document;
041: import org.apache.jetspeed.om.page.psml.AbstractBaseElement;
042: import org.apache.jetspeed.page.PageNotFoundException;
043: import org.apache.jetspeed.page.document.DocumentException;
044: import org.apache.jetspeed.page.document.DocumentHandlerFactory;
045: import org.apache.jetspeed.page.document.DocumentNotFoundException;
046: import org.apache.jetspeed.page.document.FailedToDeleteDocumentException;
047: import org.apache.jetspeed.page.document.FailedToUpdateDocumentException;
048: import org.apache.jetspeed.page.document.Node;
049: import org.apache.jetspeed.page.document.NodeException;
050: import org.apache.xml.serialize.OutputFormat;
051: import org.apache.xml.serialize.Serializer;
052: import org.apache.xml.serialize.XMLSerializer;
053: import org.castor.mapping.BindingType;
054: import org.castor.mapping.MappingUnmarshaller;
055: import org.exolab.castor.mapping.Mapping;
056: import org.exolab.castor.mapping.MappingException;
057: import org.exolab.castor.mapping.MappingLoader;
058: import org.exolab.castor.xml.ClassDescriptorResolver;
059: import org.exolab.castor.xml.ClassDescriptorResolverFactory;
060: import org.exolab.castor.xml.MarshalException;
061: import org.exolab.castor.xml.Marshaller;
062: import org.exolab.castor.xml.SAX2EventProducer;
063: import org.exolab.castor.xml.Unmarshaller;
064: import org.exolab.castor.xml.ValidationException;
065: import org.exolab.castor.xml.XMLClassDescriptorResolver;
066: import org.xml.sax.Attributes;
067: import org.xml.sax.ContentHandler;
068: import org.xml.sax.InputSource;
069: import org.xml.sax.Locator;
070: import org.xml.sax.SAXException;
071: import org.xml.sax.XMLReader;
072:
073: /**
074: * <p>
075: * CastorFileSystemDocumentHandler
076: * </p>
077: * <p>
078: *
079: * </p>
080: *
081: * @author <a href="mailto:weaver@apache.org">Scott T. Weaver </a>
082: * @version $Id: CastorFileSystemDocumentHandler.java 568113 2007-08-21 13:04:07Z woonsan $
083: *
084: */
085: public class CastorFileSystemDocumentHandler implements
086: org.apache.jetspeed.page.document.DocumentHandler,
087: FileCacheEventListener {
088: private final static Log log = LogFactory
089: .getLog(CastorFileSystemDocumentHandler.class);
090:
091: private final static String PSML_DOCUMENT_ENCODING = "UTF-8";
092:
093: protected String documentType;
094: protected Class expectedReturnType;
095: protected String documentRoot;
096: protected File documentRootDir;
097: protected FileCache fileCache;
098:
099: private OutputFormat format;
100: private final XMLReader xmlReader;
101: private DocumentHandlerFactory handlerFactory;
102: private ClassDescriptorResolver classDescriptorResolver;
103:
104: /**
105: *
106: * @param mappingFile
107: * Castor mapping file. THe mapping file must be in the class
108: * path
109: * @param documentType
110: * @param expectedReturnType
111: * @throws FileNotFoundException
112: */
113: public CastorFileSystemDocumentHandler(String mappingFile,
114: String documentType, Class expectedReturnType,
115: String documentRoot, FileCache fileCache)
116: throws FileNotFoundException, SAXException,
117: ParserConfigurationException, MappingException {
118: super ();
119: this .documentType = documentType;
120: this .expectedReturnType = expectedReturnType;
121: this .documentRoot = documentRoot;
122: this .documentRootDir = new File(documentRoot);
123: verifyPath(documentRootDir);
124: this .fileCache = fileCache;
125: this .fileCache.addListener(this );
126: this .format = new OutputFormat();
127: format.setIndenting(true);
128: format.setIndent(4);
129: format.setEncoding(PSML_DOCUMENT_ENCODING);
130:
131: SAXParserFactory factory = SAXParserFactory.newInstance();
132: SAXParser parser = factory.newSAXParser();
133:
134: xmlReader = parser.getXMLReader();
135: xmlReader.setFeature("http://xml.org/sax/features/namespaces",
136: false);
137:
138: /*
139: * Create ClassDescripterResolver for better performance.
140: * Mentioned as 'best practice' on the Castor website.
141: */
142: createCastorClassDescriptorResolver(mappingFile);
143: }
144:
145: public CastorFileSystemDocumentHandler(String mappingFile,
146: String documentType, String expectedReturnType,
147: String documentRoot, FileCache fileCache)
148: throws FileNotFoundException, ClassNotFoundException,
149: SAXException, ParserConfigurationException,
150: MappingException {
151: this (mappingFile, documentType, Class
152: .forName(expectedReturnType), documentRoot, fileCache);
153: }
154:
155: /**
156: * <p>
157: * getDocument
158: * </p>
159: *
160: * @see org.apache.jetspeed.page.document.DocumentHandler#getDocument(java.lang.String)
161: * @param name
162: * @return @throws
163: * DocumentNotFoundException
164: * @throws DocumentException,
165: * DocumentNotFoundException
166: */
167: public Document getDocument(String name) throws NodeException,
168: DocumentNotFoundException {
169: return getDocument(name, true);
170: }
171:
172: public void updateDocument(Document document)
173: throws FailedToUpdateDocumentException {
174: updateDocument(document, false);
175: }
176:
177: /**
178: * <p>
179: * updateDocument
180: * </p>
181: *
182: * @see org.apache.jetspeed.page.document.DocumentHandler#updateDocument(org.apache.jetspeed.om.page.Document)
183: * @param document
184: * @param systemUpdate
185: */
186: protected void updateDocument(Document document,
187: boolean systemUpdate)
188: throws FailedToUpdateDocumentException {
189: // sanity checks
190: if (document == null) {
191: log.warn("Recieved null Document to update");
192: return;
193: }
194: String path = document.getPath();
195: if (path == null) {
196: path = document.getId();
197: if (path == null) {
198: log
199: .warn("Recieved Document with null path/id to update");
200: return;
201: }
202: document.setPath(path);
203: }
204: AbstractBaseElement documentImpl = (AbstractBaseElement) document;
205: documentImpl.setHandlerFactory(handlerFactory);
206: if (systemUpdate) {
207: // on system update: temporarily turn off security
208: documentImpl.setPermissionsEnabled(false);
209: documentImpl.setConstraintsEnabled(false);
210: } else {
211: documentImpl.setPermissionsEnabled(handlerFactory
212: .getPermissionsEnabled());
213: documentImpl.setConstraintsEnabled(handlerFactory
214: .getConstraintsEnabled());
215: }
216: documentImpl.marshalling();
217:
218: // marshal page to disk
219: String fileName = path;
220: if (!fileName.endsWith(this .documentType)) {
221: fileName = path + this .documentType;
222: }
223: File f = new File(this .documentRootDir, fileName);
224: Writer writer = null;
225:
226: try {
227: // marshal: use SAX II handler to filter document XML for
228: // page and folder menu definition menu elements ordered
229: // polymorphic collection to strip artifical <menu-element>
230: // tags enabling Castor XML binding; see JETSPEED-INF/castor/page-mapping.xml
231: writer = new OutputStreamWriter(new FileOutputStream(f),
232: PSML_DOCUMENT_ENCODING);
233: Serializer serializer = new XMLSerializer(writer,
234: this .format);
235: final ContentHandler handler = serializer
236: .asContentHandler();
237:
238: Marshaller marshaller = new Marshaller(
239: new ContentHandler() {
240: private int menuDepth = 0;
241:
242: public void characters(char[] ch, int start,
243: int length) throws SAXException {
244: handler.characters(ch, start, length);
245: }
246:
247: public void endDocument() throws SAXException {
248: handler.endDocument();
249: }
250:
251: public void ignorableWhitespace(char[] ch,
252: int start, int length)
253: throws SAXException {
254: handler.ignorableWhitespace(ch, start,
255: length);
256: }
257:
258: public void processingInstruction(
259: String target, String data)
260: throws SAXException {
261: handler.processingInstruction(target, data);
262: }
263:
264: public void setDocumentLocator(Locator locator) {
265: handler.setDocumentLocator(locator);
266: }
267:
268: public void startDocument() throws SAXException {
269: handler.startDocument();
270: }
271:
272: public void endElement(String uri,
273: String localName, String qName)
274: throws SAXException {
275: // track menu depth
276: if (qName.equals("menu")) {
277: menuDepth--;
278: }
279:
280: // filter menu-element noded within menu definition
281: if ((menuDepth == 0)
282: || !qName.equals("menu-element")) {
283: handler.endElement(uri, localName,
284: qName);
285: }
286: }
287:
288: public void endPrefixMapping(String prefix)
289: throws SAXException {
290: }
291:
292: public void skippedEntity(String name)
293: throws SAXException {
294: handler.skippedEntity(name);
295: }
296:
297: public void startElement(String uri,
298: String localName, String qName,
299: Attributes atts) throws SAXException {
300: // filter menu-element noded within menu definition
301: if ((menuDepth == 0)
302: || !qName.equals("menu-element")) {
303: handler.startElement(uri, localName,
304: qName, atts);
305: }
306:
307: // track menu depth
308: if (qName.equals("menu")) {
309: menuDepth++;
310: }
311: }
312:
313: public void startPrefixMapping(String prefix,
314: String uri) throws SAXException {
315: }
316: });
317: marshaller
318: .setResolver((XMLClassDescriptorResolver) classDescriptorResolver);
319:
320: marshaller.setValidation(false); // results in better performance
321: marshaller.marshal(document);
322: } catch (MarshalException e) {
323: log.error("Could not marshal the file "
324: + f.getAbsolutePath(), e);
325: throw new FailedToUpdateDocumentException(e);
326: } catch (ValidationException e) {
327: log.error("Document " + f.getAbsolutePath()
328: + " is not valid", e);
329: throw new FailedToUpdateDocumentException(e);
330: } catch (IOException e) {
331: log.error("Could not save the file " + f.getAbsolutePath(),
332: e);
333: throw new FailedToUpdateDocumentException(e);
334: } catch (Exception e) {
335: log.error("Error while saving " + f.getAbsolutePath(), e);
336: throw new FailedToUpdateDocumentException(e);
337: } finally {
338: if (systemUpdate) {
339: // restore permissions / constraints
340: documentImpl.setPermissionsEnabled(handlerFactory
341: .getPermissionsEnabled());
342: documentImpl.setConstraintsEnabled(handlerFactory
343: .getConstraintsEnabled());
344: }
345: try {
346: writer.close();
347: } catch (IOException e) {
348: }
349: }
350:
351: }
352:
353: protected void createCastorClassDescriptorResolver(
354: String mappingFile) throws MappingException {
355: Mapping mapping = null;
356: try {
357: InputStream stream = getClass().getResourceAsStream(
358: mappingFile);
359:
360: if (log.isDebugEnabled()) {
361: log.debug("Loading psml mapping file " + mappingFile);
362: }
363:
364: mapping = new Mapping();
365:
366: InputSource is = new InputSource(stream);
367:
368: is.setSystemId(mappingFile);
369: mapping.loadMapping(is);
370: } catch (Exception e) {
371: IllegalStateException ise = new IllegalStateException(
372: "Error in psml mapping creation");
373: ise.initCause(e);
374: throw ise;
375: }
376: this .classDescriptorResolver = ClassDescriptorResolverFactory
377: .createClassDescriptorResolver(BindingType.XML);
378: MappingUnmarshaller mappingUnmarshaller = new MappingUnmarshaller();
379: MappingLoader mappingLoader = mappingUnmarshaller
380: .getMappingLoader(mapping, BindingType.XML);
381: classDescriptorResolver.setMappingLoader(mappingLoader);
382: }
383:
384: protected Object unmarshallDocument(Class clazz, String path,
385: String extension) throws DocumentNotFoundException,
386: DocumentException {
387: Document document = null;
388: File f = null;
389: if (path.endsWith(extension)) {
390: f = new File(this .documentRootDir, path);
391: } else {
392: f = new File(this .documentRootDir, path + extension);
393: }
394:
395: if (!f.exists()) {
396: throw new PageNotFoundException("Document not found: "
397: + path);
398: }
399:
400: try {
401: // unmarshal: use SAX II parser to read document XML, filtering
402: // for page and folder menu definition menu elements ordered
403: // polymorphic collection to insert artifical <menu-element>
404: // tags enabling Castor XML binding; see JETSPEED-INF/castor/page-mapping.xml
405:
406: final InputSource readerInput = new InputSource(
407: new InputStreamReader(new FileInputStream(f),
408: PSML_DOCUMENT_ENCODING));
409: Unmarshaller unmarshaller = new Unmarshaller();
410: unmarshaller
411: .setResolver((XMLClassDescriptorResolver) classDescriptorResolver);
412: unmarshaller.setValidation(false); // results in better performance
413:
414: synchronized (xmlReader) {
415: document = (Document) unmarshaller
416: .unmarshal(new SAX2EventProducer() {
417: public void setContentHandler(
418: final ContentHandler handler) {
419: xmlReader
420: .setContentHandler(new ContentHandler() {
421: private int menuDepth = 0;
422:
423: public void characters(
424: char[] ch,
425: int start,
426: int length)
427: throws SAXException {
428: handler.characters(ch,
429: start, length);
430: }
431:
432: public void endDocument()
433: throws SAXException {
434: handler.endDocument();
435: }
436:
437: public void ignorableWhitespace(
438: char[] ch,
439: int start,
440: int length)
441: throws SAXException {
442: handler
443: .ignorableWhitespace(
444: ch,
445: start,
446: length);
447: }
448:
449: public void processingInstruction(
450: String target,
451: String data)
452: throws SAXException {
453: handler
454: .processingInstruction(
455: target,
456: data);
457: }
458:
459: public void setDocumentLocator(
460: Locator locator) {
461: handler
462: .setDocumentLocator(locator);
463: }
464:
465: public void startDocument()
466: throws SAXException {
467: handler.startDocument();
468: }
469:
470: public void endElement(
471: String uri,
472: String localName,
473: String qName)
474: throws SAXException {
475: // always include all elements
476: handler.endElement(uri,
477: localName,
478: qName);
479: // track menu depth and insert menu-element nodes
480: // to encapsulate menu elements to support collection
481: // polymorphism in Castor
482: if (qName
483: .equals("menu")) {
484: menuDepth--;
485: if (menuDepth > 0) {
486: handler
487: .endElement(
488: null,
489: null,
490: "menu-element");
491: }
492: } else if ((menuDepth > 0)
493: && (qName
494: .equals("options")
495: || qName
496: .equals("separator")
497: || qName
498: .equals("include") || qName
499: .equals("exclude"))) {
500: handler
501: .endElement(
502: null,
503: null,
504: "menu-element");
505: }
506: }
507:
508: public void endPrefixMapping(
509: String prefix)
510: throws SAXException {
511: }
512:
513: public void skippedEntity(
514: String name)
515: throws SAXException {
516: handler
517: .skippedEntity(name);
518: }
519:
520: public void startElement(
521: String uri,
522: String localName,
523: String qName,
524: Attributes atts)
525: throws SAXException {
526: // track menu depth and insert menu-element nodes
527: // to encapsulate menu elements to support collection
528: // polymorphism in Castor
529:
530: if (qName
531: .equals("menu")) {
532: if (menuDepth > 0) {
533: handler
534: .startElement(
535: null,
536: null,
537: "menu-element",
538: null);
539: }
540: menuDepth++;
541: } else if ((menuDepth > 0)
542: && (qName
543: .equals("options")
544: || qName
545: .equals("separator")
546: || qName
547: .equals("include") || qName
548: .equals("exclude"))) {
549: handler
550: .startElement(
551: null,
552: null,
553: "menu-element",
554: null);
555: }
556:
557: // always include all elements
558: handler.startElement(
559: null, null,
560: qName, atts);
561: }
562:
563: public void startPrefixMapping(
564: String prefix,
565: String uri)
566: throws SAXException {
567: }
568: });
569: }
570:
571: public void start() throws SAXException {
572: try {
573: xmlReader.parse(readerInput);
574: } catch (IOException ioe) {
575: throw new SAXException(ioe);
576: }
577: }
578: });
579: }
580:
581: document.setPath(path);
582: AbstractBaseElement documentImpl = (AbstractBaseElement) document;
583: documentImpl.setHandlerFactory(handlerFactory);
584: documentImpl.setPermissionsEnabled(handlerFactory
585: .getPermissionsEnabled());
586: documentImpl.setConstraintsEnabled(handlerFactory
587: .getConstraintsEnabled());
588: documentImpl.unmarshalled();
589: if (document.isDirty()) {
590: updateDocument(document, true);
591: document.setDirty(false);
592: }
593: } catch (IOException e) {
594: log.error("Could not load the file " + f.getAbsolutePath(),
595: e);
596: throw new PageNotFoundException("Could not load the file "
597: + f.getAbsolutePath(), e);
598: } catch (MarshalException e) {
599: log.error("Could not unmarshal the file "
600: + f.getAbsolutePath(), e);
601: throw new PageNotFoundException(
602: "Could not unmarshal the file "
603: + f.getAbsolutePath(), e);
604: } catch (ValidationException e) {
605: log.error("Document " + f.getAbsolutePath()
606: + " is not valid", e);
607: throw new DocumentNotFoundException("Document "
608: + f.getAbsolutePath() + " is not valid", e);
609: }
610:
611: if (document == null) {
612: throw new DocumentNotFoundException("Document not found: "
613: + path);
614: } else {
615: if (!clazz.isAssignableFrom(document.getClass())) {
616: throw new ClassCastException(document.getClass()
617: .getName()
618: + " must implement or extend "
619: + clazz.getName());
620: }
621: return document;
622: }
623: }
624:
625: protected void verifyPath(File path) throws FileNotFoundException {
626: if (path == null) {
627: throw new IllegalArgumentException(
628: "Page root cannot be null");
629: }
630:
631: if (!path.exists()) {
632: throw new FileNotFoundException(
633: "Could not locate root pages path "
634: + path.getAbsolutePath());
635: }
636: }
637:
638: /**
639: * <p>
640: * removeDocument
641: * </p>
642: *
643: * @see org.apache.jetspeed.page.document.DocumentHandler#removeDocument(org.apache.jetspeed.om.page.Document)
644: * @param document
645: * @throws DocumentNotFoundException
646: * @throws FailedToDeleteDocumentException
647: */
648: public void removeDocument(Document document)
649: throws DocumentNotFoundException,
650: FailedToDeleteDocumentException {
651: // sanity checks
652: if (document == null) {
653: log.warn("Recieved null Document to remove");
654: return;
655: }
656: String path = document.getPath();
657: if (path == null) {
658: path = document.getId();
659: if (path == null) {
660: log
661: .warn("Recieved Document with null path/id to remove");
662: return;
663: }
664: }
665:
666: // remove page from disk
667: String fileName = path;
668: if (!fileName.endsWith(this .documentType)) {
669: fileName = path + this .documentType;
670: }
671: File file = new File(this .documentRootDir, fileName);
672: if (!file.delete()) {
673: throw new FailedToDeleteDocumentException(file
674: .getAbsolutePath()
675: + " document cannot be deleted.");
676: }
677:
678: // remove from cache
679: fileCache.remove(path);
680:
681: // reset document
682: AbstractNode documentImpl = (AbstractNode) document;
683: documentImpl.setParent(null);
684: }
685:
686: /**
687: * <p>
688: * getDocument
689: * </p>
690: *
691: * @see org.apache.jetspeed.page.document.DocumentHandler#getDocument(java.lang.String,
692: * boolean)
693: * @param name
694: * @param fromCahe
695: * Whether or not the Document should be pulled from the cache.
696: * @return @throws
697: * DocumentNotFoundException
698: */
699: public Document getDocument(String name, boolean fromCache)
700: throws DocumentNotFoundException, NodeException {
701: Document document = null;
702: if (fromCache) {
703: Object obj = fileCache.getDocument(name);
704: document = (Document) obj;
705: if (document == null) {
706: document = (Document) unmarshallDocument(
707: expectedReturnType, name, documentType);
708: addToCache(name, document);
709: }
710: } else {
711: document = (Document) unmarshallDocument(
712: expectedReturnType, name, documentType);
713: }
714:
715: return document;
716: }
717:
718: /**
719: * <p>
720: * addToCache
721: * </p>
722: *
723: * @param path
724: * @param objectToCache
725: */
726: protected void addToCache(String path, Object objectToCache) {
727: synchronized (fileCache) {
728: // store the document in the hash and reference it to the
729: // watcher
730: try {
731: fileCache
732: .put(path, objectToCache, this .documentRootDir);
733:
734: } catch (java.io.IOException e) {
735: log.error("Error putting document: " + e);
736: IllegalStateException ise = new IllegalStateException(
737: "Error storing Document in the FileCache: "
738: + e.toString());
739: ise.initCause(e);
740: }
741: }
742: }
743:
744: /**
745: * <p>
746: * refresh
747: * </p>
748: *
749: * @see org.apache.jetspeed.cache.file.FileCacheEventListener#refresh(org.apache.jetspeed.cache.file.FileCacheEntry)
750: * @param entry
751: * @throws Exception
752: */
753: public void refresh(FileCacheEntry entry) throws Exception {
754: log.debug("Entry is refreshing: " + entry.getFile().getName());
755:
756: if (entry.getDocument() instanceof Document
757: && ((Document) entry.getDocument()).getPath().endsWith(
758: documentType)) {
759: Document document = (Document) entry.getDocument();
760: Document freshDoc = getDocument(document.getPath(), false);
761: Node parent = ((AbstractNode) document).getParent(false);
762:
763: freshDoc.setParent(parent);
764: if (parent instanceof FolderImpl) {
765: FolderImpl folder = (FolderImpl) parent;
766: folder.getAllNodes().add(freshDoc);
767: }
768:
769: freshDoc.setPath(document.getPath());
770: entry.setDocument(freshDoc);
771: }
772:
773: }
774:
775: /**
776: * <p>
777: * evict
778: * </p>
779: *
780: * @see org.apache.jetspeed.cache.file.FileCacheEventListener#evict(org.apache.jetspeed.cache.file.FileCacheEntry)
781: * @param entry
782: * @throws Exception
783: */
784: public void evict(FileCacheEntry entry) throws Exception {
785: // TODO Auto-generated method stub
786:
787: }
788:
789: /**
790: * <p>
791: * getType
792: * </p>
793: *
794: * @see org.apache.jetspeed.page.document.DocumentHandler#getType()
795: * @return
796: */
797: public String getType() {
798: return documentType;
799: }
800:
801: /**
802: * <p>
803: * getHandlerFactory
804: * </p>
805: *
806: * @see org.apache.jetspeed.page.document.DocumentHandler#getHandlerFactory()
807: * @return
808: */
809: public DocumentHandlerFactory getHandlerFactory() {
810: return handlerFactory;
811: }
812:
813: /**
814: * <p>
815: * setHandlerFactory
816: * </p>
817: *
818: * @see org.apache.jetspeed.page.document.DocumentHandler#setHandlerFactory(org.apache.jetspeed.page.document.DocumentHandlerFactory)
819: * @param factory
820: */
821: public void setHandlerFactory(DocumentHandlerFactory factory) {
822: this.handlerFactory = factory;
823: }
824:
825: }
|