View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.page.document.psml;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.FileNotFoundException;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.io.OutputStreamWriter;
27  import java.io.Writer;
28  
29  import javax.xml.parsers.ParserConfigurationException;
30  import javax.xml.parsers.SAXParser;
31  import javax.xml.parsers.SAXParserFactory;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.jetspeed.cache.file.FileCache;
36  import org.apache.jetspeed.cache.file.FileCacheEntry;
37  import org.apache.jetspeed.cache.file.FileCacheEventListener;
38  import org.apache.jetspeed.om.common.SecurityConstraints;
39  import org.apache.jetspeed.om.folder.psml.FolderImpl;
40  import org.apache.jetspeed.om.page.Document;
41  import org.apache.jetspeed.om.page.psml.AbstractBaseElement;
42  import org.apache.jetspeed.page.PageNotFoundException;
43  import org.apache.jetspeed.page.document.DocumentException;
44  import org.apache.jetspeed.page.document.DocumentHandlerFactory;
45  import org.apache.jetspeed.page.document.DocumentNotFoundException;
46  import org.apache.jetspeed.page.document.FailedToDeleteDocumentException;
47  import org.apache.jetspeed.page.document.FailedToUpdateDocumentException;
48  import org.apache.jetspeed.page.document.Node;
49  import org.apache.jetspeed.page.document.NodeException;
50  import org.apache.xml.serialize.OutputFormat;
51  import org.apache.xml.serialize.Serializer;
52  import org.apache.xml.serialize.XMLSerializer;
53  import org.castor.mapping.BindingType;
54  import org.castor.mapping.MappingUnmarshaller;
55  import org.exolab.castor.mapping.Mapping;
56  import org.exolab.castor.mapping.MappingException;
57  import org.exolab.castor.mapping.MappingLoader;
58  import org.exolab.castor.xml.ClassDescriptorResolver;
59  import org.exolab.castor.xml.ClassDescriptorResolverFactory;
60  import org.exolab.castor.xml.MarshalException;
61  import org.exolab.castor.xml.Marshaller;
62  import org.exolab.castor.xml.SAX2EventProducer;
63  import org.exolab.castor.xml.Unmarshaller;
64  import org.exolab.castor.xml.ValidationException;
65  import org.exolab.castor.xml.XMLClassDescriptorResolver;
66  import org.xml.sax.Attributes;
67  import org.xml.sax.ContentHandler;
68  import org.xml.sax.InputSource;
69  import org.xml.sax.Locator;
70  import org.xml.sax.SAXException;
71  import org.xml.sax.XMLReader;
72  
73  /***
74   * <p>
75   * CastorFileSystemDocumentHandler
76   * </p>
77   * <p>
78   * 
79   * </p>
80   * 
81   * @author <a href="mailto:weaver@apache.org">Scott T. Weaver </a>
82   * @version $Id: CastorFileSystemDocumentHandler.java 568113 2007-08-21 13:04:07Z woonsan $
83   *  
84   */
85  public class CastorFileSystemDocumentHandler implements org.apache.jetspeed.page.document.DocumentHandler, FileCacheEventListener
86  {
87      private final static Log log = LogFactory.getLog(CastorFileSystemDocumentHandler.class);
88  
89      private final static String PSML_DOCUMENT_ENCODING = "UTF-8";
90  
91      protected String documentType;
92      protected Class expectedReturnType;
93      protected String documentRoot;
94      protected File documentRootDir;
95      protected FileCache fileCache;
96      
97      private OutputFormat format;
98      private final XMLReader xmlReader;
99      private DocumentHandlerFactory handlerFactory;
100     private ClassDescriptorResolver classDescriptorResolver;
101 
102     /***
103      * 
104      * @param mappingFile
105      *            Castor mapping file. THe mapping file must be in the class
106      *            path
107      * @param documentType
108      * @param expectedReturnType
109      * @throws FileNotFoundException
110      */
111     public CastorFileSystemDocumentHandler( String mappingFile, String documentType, Class expectedReturnType,
112             String documentRoot, FileCache fileCache ) throws FileNotFoundException,SAXException,ParserConfigurationException, MappingException
113     {
114         super();
115         this.documentType = documentType;
116         this.expectedReturnType = expectedReturnType;
117         this.documentRoot = documentRoot;
118         this.documentRootDir = new File(documentRoot);
119         verifyPath(documentRootDir);
120         this.fileCache = fileCache;
121         this.fileCache.addListener(this);
122         this.format = new OutputFormat();
123         format.setIndenting(true);
124         format.setIndent(4);
125         format.setEncoding(PSML_DOCUMENT_ENCODING);
126         
127         SAXParserFactory factory = SAXParserFactory.newInstance();
128         SAXParser parser = factory.newSAXParser();
129         
130         xmlReader = parser.getXMLReader();
131         xmlReader.setFeature("http://xml.org/sax/features/namespaces", false);
132         
133         /*
134          * Create ClassDescripterResolver for better performance. 
135          * Mentioned as 'best practice' on the Castor website.
136          */
137         createCastorClassDescriptorResolver(mappingFile);
138     }
139     
140     public CastorFileSystemDocumentHandler( String mappingFile, String documentType, String expectedReturnType,
141             String documentRoot, FileCache fileCache ) throws FileNotFoundException, ClassNotFoundException,SAXException,ParserConfigurationException, MappingException
142     {
143         this(mappingFile, documentType, Class.forName(expectedReturnType), documentRoot, fileCache);
144     }
145 
146     /***
147      * <p>
148      * getDocument
149      * </p>
150      * 
151      * @see org.apache.jetspeed.page.document.DocumentHandler#getDocument(java.lang.String)
152      * @param name
153      * @return @throws
154      *         DocumentNotFoundException
155      * @throws DocumentException,
156      *             DocumentNotFoundException
157      */
158     public Document getDocument( String name ) throws NodeException, DocumentNotFoundException
159     {
160         return getDocument(name, true);
161     }
162     
163     public void updateDocument( Document document ) throws FailedToUpdateDocumentException
164     {
165     	updateDocument(document, false);
166     }
167     
168     /***
169      * <p>
170      * updateDocument
171      * </p>
172      * 
173      * @see org.apache.jetspeed.page.document.DocumentHandler#updateDocument(org.apache.jetspeed.om.page.Document)
174      * @param document
175      * @param systemUpdate 
176      */
177     protected void updateDocument( Document document, boolean systemUpdate) throws FailedToUpdateDocumentException
178     {
179         // sanity checks
180         if (document == null)
181         {
182             log.warn("Recieved null Document to update");
183             return;
184         }
185         String path = document.getPath();
186         if (path == null)
187         {
188             path = document.getId();
189             if (path == null)
190             {
191                 log.warn("Recieved Document with null path/id to update");
192                 return;
193             }
194             document.setPath(path);
195         }
196         AbstractBaseElement documentImpl = (AbstractBaseElement)document;
197         documentImpl.setHandlerFactory(handlerFactory);
198         if (systemUpdate){
199         	// on system update: temporarily turn off security
200             documentImpl.setPermissionsEnabled(false);
201             documentImpl.setConstraintsEnabled(false);
202         } else {
203             documentImpl.setPermissionsEnabled(handlerFactory.getPermissionsEnabled());
204             documentImpl.setConstraintsEnabled(handlerFactory.getConstraintsEnabled());
205         }
206         documentImpl.marshalling();
207         
208         // marshal page to disk
209         String fileName = path;        
210         if (!fileName.endsWith(this.documentType))
211         {
212             fileName = path + this.documentType;
213         }
214         File f = new File(this.documentRootDir, fileName);
215         Writer writer = null;
216 
217         try
218         {
219             // marshal: use SAX II handler to filter document XML for
220             // page and folder menu definition menu elements ordered
221             // polymorphic collection to strip artifical <menu-element>
222             // tags enabling Castor XML binding; see JETSPEED-INF/castor/page-mapping.xml
223             writer = new OutputStreamWriter(new FileOutputStream(f), PSML_DOCUMENT_ENCODING);
224             Serializer serializer = new XMLSerializer(writer, this.format);
225             final ContentHandler handler = serializer.asContentHandler();
226             
227             Marshaller marshaller = new Marshaller(new ContentHandler()
228                 {
229                     private int menuDepth = 0;
230                     
231                     public void characters(char[] ch, int start, int length) throws SAXException
232                     {
233                         handler.characters(ch, start, length);
234                     }
235 
236                     public void endDocument() throws SAXException
237                     {
238                         handler.endDocument();
239                     }
240                     
241                     public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException
242                     {
243                         handler.ignorableWhitespace(ch, start, length);
244                     }
245                     
246                     public void processingInstruction(String target, String data) throws SAXException
247                     {
248                         handler.processingInstruction(target, data);
249                     }
250                     
251                     public void setDocumentLocator(Locator locator)
252                     {
253                         handler.setDocumentLocator(locator);
254                     }
255                     
256                     public void startDocument() throws SAXException
257                     {
258                         handler.startDocument();
259                     }
260                     
261 					public void endElement(String uri, String localName, String qName) throws SAXException {
262                         // track menu depth
263                         if (qName.equals("menu"))
264                         {
265                             menuDepth--;
266                         }
267 
268                         // filter menu-element noded within menu definition
269                         if ((menuDepth == 0) || !qName.equals("menu-element"))
270                         {
271                             handler.endElement(uri, localName, qName);
272                         }
273 					}
274 
275 					public void endPrefixMapping(String prefix) throws SAXException {
276 					}
277 
278 					public void skippedEntity(String name) throws SAXException {
279 						handler.skippedEntity(name);
280 					}
281 
282 					public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
283                         // filter menu-element noded within menu definition
284                         if ((menuDepth == 0) || !qName.equals("menu-element"))
285                         {
286                             handler.startElement(uri,localName, qName, atts);
287                         }
288 
289                         // track menu depth
290                         if (qName.equals("menu"))
291                         {
292                             menuDepth++;
293                         }
294 					}
295 
296 					public void startPrefixMapping(String prefix, String uri) throws SAXException {
297 					}
298                 });
299             marshaller.setResolver((XMLClassDescriptorResolver) classDescriptorResolver);
300             
301             marshaller.setValidation(false); // results in better performance
302             marshaller.marshal(document);
303         }
304         catch (MarshalException e)
305         {
306             log.error("Could not marshal the file " + f.getAbsolutePath(), e);
307             throw new FailedToUpdateDocumentException(e);
308         }
309         catch (ValidationException e)
310         {
311             log.error("Document " + f.getAbsolutePath() + " is not valid", e);
312             throw new FailedToUpdateDocumentException(e);
313         }
314         catch (IOException e)
315         {
316             log.error("Could not save the file " + f.getAbsolutePath(), e);
317             throw new FailedToUpdateDocumentException(e);
318         }
319         catch (Exception e)
320         {
321             log.error("Error while saving  " + f.getAbsolutePath(), e);
322             throw new FailedToUpdateDocumentException(e);
323         }
324         finally
325         {
326             if (systemUpdate){
327             	// restore permissions / constraints
328             	documentImpl.setPermissionsEnabled(handlerFactory.getPermissionsEnabled());
329                 documentImpl.setConstraintsEnabled(handlerFactory.getConstraintsEnabled());
330             }
331         	try
332             {
333                 writer.close();
334             }
335             catch (IOException e)
336             {
337             }
338         }
339 
340     }
341 
342     protected void createCastorClassDescriptorResolver(String mappingFile) throws MappingException
343     {
344     	Mapping mapping=null;
345     	try
346         {
347             InputStream stream = getClass().getResourceAsStream(mappingFile);
348 
349             if (log.isDebugEnabled())
350             {
351                 log.debug("Loading psml mapping file " + mappingFile);
352             }
353 
354             mapping = new Mapping();
355 
356             InputSource is = new InputSource(stream);
357 
358             is.setSystemId(mappingFile);
359             mapping.loadMapping(is);
360         }
361         catch (Exception e)
362         {
363             IllegalStateException ise = new IllegalStateException("Error in psml mapping creation");
364             ise.initCause(e);
365             throw ise;
366         }
367         this.classDescriptorResolver =
368  		   ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML);
369  		MappingUnmarshaller mappingUnmarshaller = new MappingUnmarshaller();
370  		MappingLoader mappingLoader = mappingUnmarshaller.getMappingLoader(mapping, BindingType.XML);
371  		classDescriptorResolver.setMappingLoader(mappingLoader);
372     }
373 
374     protected Object unmarshallDocument( Class clazz, String path, String extension ) throws DocumentNotFoundException,
375             DocumentException
376     {
377         Document document = null;
378         File f = null;
379         if (path.endsWith(extension))
380         {
381             f = new File(this.documentRootDir, path);
382         }
383         else
384         {
385             f = new File(this.documentRootDir, path + extension);
386         }
387 
388         if (!f.exists())
389         {
390             throw new PageNotFoundException("Document not found: " + path);
391         }
392 
393         try
394         {
395             // unmarshal: use SAX II parser to read document XML, filtering
396             // for page and folder menu definition menu elements ordered
397             // polymorphic collection to insert artifical <menu-element>
398             // tags enabling Castor XML binding; see JETSPEED-INF/castor/page-mapping.xml
399             
400             final InputSource readerInput = new InputSource(new InputStreamReader(new FileInputStream(f), PSML_DOCUMENT_ENCODING));
401             Unmarshaller unmarshaller = new Unmarshaller();
402             unmarshaller.setResolver((XMLClassDescriptorResolver) classDescriptorResolver);            
403             unmarshaller.setValidation(false); // results in better performance
404             
405             synchronized (xmlReader)
406             {
407                 document = (Document) unmarshaller.unmarshal(new SAX2EventProducer()
408                     {
409                         public void setContentHandler(final ContentHandler handler)
410                         {
411                         	xmlReader.setContentHandler(new ContentHandler()
412                                 {
413                                     private int menuDepth = 0;
414 
415                                     public void characters(char[] ch, int start, int length) throws SAXException
416                                     {
417                                         handler.characters(ch, start, length);
418                                     }
419 
420                                     public void endDocument() throws SAXException
421                                     {
422                                         handler.endDocument();
423                                     }
424 
425                                     public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException
426                                     {
427                                         handler.ignorableWhitespace(ch, start, length);
428                                     }
429 
430                                     public void processingInstruction(String target, String data) throws SAXException
431                                     {
432                                         handler.processingInstruction(target, data);
433                                     }
434 
435                                     public void setDocumentLocator(Locator locator)
436                                     {
437                                         handler.setDocumentLocator(locator);
438                                     }
439 
440                                     public void startDocument() throws SAXException
441                                     {
442                                         handler.startDocument();
443                                     }
444 
445     								public void endElement(String uri, String localName, String qName) throws SAXException {
446                                         // always include all elements
447                                         handler.endElement(uri,localName,qName);
448                                         // track menu depth and insert menu-element nodes
449                                         // to encapsulate menu elements to support collection
450                                         // polymorphism in Castor
451                                         if (qName.equals("menu"))
452                                         {
453                                             menuDepth--;
454                                             if (menuDepth > 0)
455                                             {
456                                                 handler.endElement(null,null,"menu-element");
457                                             }
458                                         }
459                                         else if ((menuDepth > 0) &&
460                                                  (qName.equals("options") || qName.equals("separator") ||
461                                                 		 qName.equals("include") || qName.equals("exclude")))
462                                         {
463                                             handler.endElement(null,null,"menu-element");
464                                         }
465     								}
466 
467     								public void endPrefixMapping(String prefix) throws SAXException {
468     								}
469 
470     								public void skippedEntity(String name) throws SAXException {
471     									handler.skippedEntity(name);
472     								}
473 
474     								public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
475                                         // track menu depth and insert menu-element nodes
476                                         // to encapsulate menu elements to support collection
477                                         // polymorphism in Castor
478     									
479     									if (qName.equals("menu"))
480                                         {
481                                             if (menuDepth > 0)
482                                             {
483                                                 handler.startElement(null,null,"menu-element", null);
484                                             }
485                                             menuDepth++;
486                                         }
487                                         else if ((menuDepth > 0) &&
488                                                  (qName.equals("options") || qName.equals("separator") ||
489                                                   qName.equals("include") || qName.equals("exclude")))
490                                         {
491                                             handler.startElement(null,null,"menu-element", null);
492                                         }
493 
494                                         // always include all elements
495                                         handler.startElement(null,null, qName, atts);
496     								}
497 
498     								public void startPrefixMapping(String prefix, String uri) throws SAXException {
499     								}
500                                 });
501                         }
502                         public void start() throws SAXException
503                         {
504                             try
505                             {
506                             	xmlReader.parse(readerInput);
507                             }
508                             catch (IOException ioe)
509                             {
510                                 throw new SAXException(ioe);
511                             }
512                         }
513                     });
514             }
515             
516             document.setPath(path);
517             AbstractBaseElement documentImpl = (AbstractBaseElement)document;
518             documentImpl.setHandlerFactory(handlerFactory);
519             documentImpl.setPermissionsEnabled(handlerFactory.getPermissionsEnabled());
520             documentImpl.setConstraintsEnabled(handlerFactory.getConstraintsEnabled());
521             documentImpl.unmarshalled();
522             if (document.isDirty()){
523                 updateDocument(document, true);
524                 document.setDirty(false);
525             }
526         }
527         catch (IOException e)
528         {
529         	log.error("Could not load the file " + f.getAbsolutePath(), e);
530             throw new PageNotFoundException("Could not load the file " + f.getAbsolutePath(), e);
531         }
532         catch (MarshalException e)
533         {
534         	log.error("Could not unmarshal the file " + f.getAbsolutePath(), e);
535             throw new PageNotFoundException("Could not unmarshal the file " + f.getAbsolutePath(), e);
536         }
537         catch (ValidationException e)
538         {
539         	log.error("Document " + f.getAbsolutePath() + " is not valid", e);
540             throw new DocumentNotFoundException("Document " + f.getAbsolutePath() + " is not valid", e);
541         }
542         
543 
544         if (document == null)
545         {
546             throw new DocumentNotFoundException("Document not found: " + path);
547         }
548         else
549         {
550             if (!clazz.isAssignableFrom(document.getClass()))
551             {
552                 throw new ClassCastException(document.getClass().getName() + " must implement or extend "
553                         + clazz.getName());
554             }
555             return document;
556         }
557     }
558 
559     protected void verifyPath( File path ) throws FileNotFoundException
560     {
561         if (path == null)
562         {
563             throw new IllegalArgumentException("Page root cannot be null");
564         }
565 
566         if (!path.exists())
567         {
568             throw new FileNotFoundException("Could not locate root pages path " + path.getAbsolutePath());
569         }
570     }
571 
572     /***
573      * <p>
574      * removeDocument
575      * </p>
576      * 
577      * @see org.apache.jetspeed.page.document.DocumentHandler#removeDocument(org.apache.jetspeed.om.page.Document)
578      * @param document
579      * @throws DocumentNotFoundException
580      * @throws FailedToDeleteDocumentException
581      */
582     public void removeDocument( Document document ) throws DocumentNotFoundException, FailedToDeleteDocumentException
583     {
584         // sanity checks
585         if (document == null)
586         {
587             log.warn("Recieved null Document to remove");
588             return;
589         }
590         String path = document.getPath();
591         if (path == null)
592         {
593             path = document.getId();
594             if (path == null)
595             {
596                 log.warn("Recieved Document with null path/id to remove");
597                 return;
598             }
599         }
600 
601         // remove page from disk
602         String fileName = path;        
603         if (!fileName.endsWith(this.documentType))
604         {
605             fileName = path + this.documentType;
606         }
607         File file = new File(this.documentRootDir, fileName);
608         if (!file.delete())
609         {
610             throw new FailedToDeleteDocumentException(file.getAbsolutePath()+" document cannot be deleted.");
611         }
612 
613         // remove from cache
614         fileCache.remove(path);
615 
616         // reset document
617         AbstractNode documentImpl = (AbstractNode)document;
618         documentImpl.setParent(null);
619     }
620 
621     /***
622      * <p>
623      * getDocument
624      * </p>
625      * 
626      * @see org.apache.jetspeed.page.document.DocumentHandler#getDocument(java.lang.String,
627      *      boolean)
628      * @param name
629      * @param fromCahe
630      *            Whether or not the Document should be pulled from the cache.
631      * @return @throws
632      *         DocumentNotFoundException
633      */
634     public Document getDocument( String name, boolean fromCache ) throws DocumentNotFoundException, NodeException
635     {
636         Document document = null;
637         if (fromCache)
638         {
639             Object obj = fileCache.getDocument(name);
640             document = (Document) obj;
641             if (document == null)
642             {
643                 document = (Document) unmarshallDocument(expectedReturnType, name, documentType);
644                 addToCache(name, document);
645             }
646         }
647         else
648         {
649             document = (Document) unmarshallDocument(expectedReturnType, name, documentType);
650         }
651 
652         return document;
653     }
654 
655     /***
656      * <p>
657      * addToCache
658      * </p>
659      * 
660      * @param path
661      * @param objectToCache
662      */
663     protected void addToCache( String path, Object objectToCache )
664     {
665         synchronized (fileCache)
666         {
667             // store the document in the hash and reference it to the
668             // watcher
669             try
670             {
671                 fileCache.put(path, objectToCache, this.documentRootDir);
672 
673             }
674             catch (java.io.IOException e)
675             {
676                 log.error("Error putting document: " + e);
677                 IllegalStateException ise = new IllegalStateException("Error storing Document in the FileCache: "
678                         + e.toString());
679                 ise.initCause(e);
680             }
681         }
682     }
683 
684     /***
685      * <p>
686      * refresh
687      * </p>
688      * 
689      * @see org.apache.jetspeed.cache.file.FileCacheEventListener#refresh(org.apache.jetspeed.cache.file.FileCacheEntry)
690      * @param entry
691      * @throws Exception
692      */
693     public void refresh( FileCacheEntry entry ) throws Exception
694     {
695         log.debug("Entry is refreshing: " + entry.getFile().getName());
696 
697         if (entry.getDocument() instanceof Document && ((Document) entry.getDocument()).getPath().endsWith(documentType))
698         {
699             Document document = (Document) entry.getDocument();
700             Document freshDoc = getDocument(document.getPath(), false);
701             Node parent = ((AbstractNode)document).getParent(false);
702  
703             freshDoc.setParent(parent);
704             if(parent instanceof FolderImpl)
705             {
706                 FolderImpl folder = (FolderImpl) parent;
707                 folder.getAllNodes().add(freshDoc);
708             }
709             
710             freshDoc.setPath(document.getPath());
711             entry.setDocument(freshDoc);            
712         }
713 
714     }
715 
716     /***
717      * <p>
718      * evict
719      * </p>
720      * 
721      * @see org.apache.jetspeed.cache.file.FileCacheEventListener#evict(org.apache.jetspeed.cache.file.FileCacheEntry)
722      * @param entry
723      * @throws Exception
724      */
725     public void evict( FileCacheEntry entry ) throws Exception
726     {
727         // TODO Auto-generated method stub
728 
729     }
730 
731     /***
732      * <p>
733      * getType
734      * </p>
735      *
736      * @see org.apache.jetspeed.page.document.DocumentHandler#getType()
737      * @return
738      */
739     public String getType()
740     {
741         return documentType;
742     }
743 
744     /***
745      * <p>
746      * getHandlerFactory
747      * </p>
748      *
749      * @see org.apache.jetspeed.page.document.DocumentHandler#getHandlerFactory()
750      * @return
751      */
752     public DocumentHandlerFactory getHandlerFactory()
753     {
754         return handlerFactory;
755     }
756 
757     /***
758      * <p>
759      * setHandlerFactory
760      * </p>
761      *
762      * @see org.apache.jetspeed.page.document.DocumentHandler#setHandlerFactory(org.apache.jetspeed.page.document.DocumentHandlerFactory)
763      * @param factory
764      */
765     public void setHandlerFactory(DocumentHandlerFactory factory)
766     {
767         this.handlerFactory = factory;
768     }
769 
770 }