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 551606 2007-06-28 16:07:53Z taylor $
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             document = (Document) unmarshaller.unmarshal(new SAX2EventProducer()
405                 {
406                     public void setContentHandler(final ContentHandler handler)
407                     {
408                     	xmlReader.setContentHandler(new ContentHandler()
409                             {
410                                 private int menuDepth = 0;
411 
412                                 public void characters(char[] ch, int start, int length) throws SAXException
413                                 {
414                                     handler.characters(ch, start, length);
415                                 }
416 
417                                 public void endDocument() throws SAXException
418                                 {
419                                     handler.endDocument();
420                                 }
421 
422                                 public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException
423                                 {
424                                     handler.ignorableWhitespace(ch, start, length);
425                                 }
426 
427                                 public void processingInstruction(String target, String data) throws SAXException
428                                 {
429                                     handler.processingInstruction(target, data);
430                                 }
431 
432                                 public void setDocumentLocator(Locator locator)
433                                 {
434                                     handler.setDocumentLocator(locator);
435                                 }
436 
437                                 public void startDocument() throws SAXException
438                                 {
439                                     handler.startDocument();
440                                 }
441 
442 								public void endElement(String uri, String localName, String qName) throws SAXException {
443                                     // always include all elements
444                                     handler.endElement(uri,localName,qName);
445                                     // track menu depth and insert menu-element nodes
446                                     // to encapsulate menu elements to support collection
447                                     // polymorphism in Castor
448                                     if (qName.equals("menu"))
449                                     {
450                                         menuDepth--;
451                                         if (menuDepth > 0)
452                                         {
453                                             handler.endElement(null,null,"menu-element");
454                                         }
455                                     }
456                                     else if ((menuDepth > 0) &&
457                                              (qName.equals("options") || qName.equals("separator") ||
458                                             		 qName.equals("include") || qName.equals("exclude")))
459                                     {
460                                         handler.endElement(null,null,"menu-element");
461                                     }
462 								}
463 
464 								public void endPrefixMapping(String prefix) throws SAXException {
465 								}
466 
467 								public void skippedEntity(String name) throws SAXException {
468 									handler.skippedEntity(name);
469 								}
470 
471 								public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
472                                     // track menu depth and insert menu-element nodes
473                                     // to encapsulate menu elements to support collection
474                                     // polymorphism in Castor
475 									
476 									if (qName.equals("menu"))
477                                     {
478                                         if (menuDepth > 0)
479                                         {
480                                             handler.startElement(null,null,"menu-element", null);
481                                         }
482                                         menuDepth++;
483                                     }
484                                     else if ((menuDepth > 0) &&
485                                              (qName.equals("options") || qName.equals("separator") ||
486                                               qName.equals("include") || qName.equals("exclude")))
487                                     {
488                                         handler.startElement(null,null,"menu-element", null);
489                                     }
490 
491                                     // always include all elements
492                                     handler.startElement(null,null, qName, atts);
493 								}
494 
495 								public void startPrefixMapping(String prefix, String uri) throws SAXException {
496 								}
497                             });
498                     }
499                     public void start() throws SAXException
500                     {
501                         try
502                         {
503                         	xmlReader.parse(readerInput);
504                         }
505                         catch (IOException ioe)
506                         {
507                             throw new SAXException(ioe);
508                         }
509                     }
510                 });
511 
512             document.setPath(path);
513             AbstractBaseElement documentImpl = (AbstractBaseElement)document;
514             documentImpl.setHandlerFactory(handlerFactory);
515             documentImpl.setPermissionsEnabled(handlerFactory.getPermissionsEnabled());
516             documentImpl.setConstraintsEnabled(handlerFactory.getConstraintsEnabled());
517             documentImpl.unmarshalled();
518             if (document.isDirty()){
519                 updateDocument(document, true);
520                 document.setDirty(false);
521             }
522         }
523         catch (IOException e)
524         {
525         	log.error("Could not load the file " + f.getAbsolutePath(), e);
526             throw new PageNotFoundException("Could not load the file " + f.getAbsolutePath(), e);
527         }
528         catch (MarshalException e)
529         {
530         	log.error("Could not unmarshal the file " + f.getAbsolutePath(), e);
531             throw new PageNotFoundException("Could not unmarshal the file " + f.getAbsolutePath(), e);
532         }
533         catch (ValidationException e)
534         {
535         	log.error("Document " + f.getAbsolutePath() + " is not valid", e);
536             throw new DocumentNotFoundException("Document " + f.getAbsolutePath() + " is not valid", e);
537         }
538         
539 
540         if (document == null)
541         {
542             throw new DocumentNotFoundException("Document not found: " + path);
543         }
544         else
545         {
546             if (!clazz.isAssignableFrom(document.getClass()))
547             {
548                 throw new ClassCastException(document.getClass().getName() + " must implement or extend "
549                         + clazz.getName());
550             }
551             return document;
552         }
553     }
554 
555     protected void verifyPath( File path ) throws FileNotFoundException
556     {
557         if (path == null)
558         {
559             throw new IllegalArgumentException("Page root cannot be null");
560         }
561 
562         if (!path.exists())
563         {
564             throw new FileNotFoundException("Could not locate root pages path " + path.getAbsolutePath());
565         }
566     }
567 
568     /***
569      * <p>
570      * removeDocument
571      * </p>
572      * 
573      * @see org.apache.jetspeed.page.document.DocumentHandler#removeDocument(org.apache.jetspeed.om.page.Document)
574      * @param document
575      * @throws DocumentNotFoundException
576      * @throws FailedToDeleteDocumentException
577      */
578     public void removeDocument( Document document ) throws DocumentNotFoundException, FailedToDeleteDocumentException
579     {
580         // sanity checks
581         if (document == null)
582         {
583             log.warn("Recieved null Document to remove");
584             return;
585         }
586         String path = document.getPath();
587         if (path == null)
588         {
589             path = document.getId();
590             if (path == null)
591             {
592                 log.warn("Recieved Document with null path/id to remove");
593                 return;
594             }
595         }
596 
597         // remove page from disk
598         String fileName = path;        
599         if (!fileName.endsWith(this.documentType))
600         {
601             fileName = path + this.documentType;
602         }
603         File file = new File(this.documentRootDir, fileName);
604         if (!file.delete())
605         {
606             throw new FailedToDeleteDocumentException(file.getAbsolutePath()+" document cannot be deleted.");
607         }
608 
609         // remove from cache
610         fileCache.remove(path);
611 
612         // reset document
613         AbstractNode documentImpl = (AbstractNode)document;
614         documentImpl.setParent(null);
615     }
616 
617     /***
618      * <p>
619      * getDocument
620      * </p>
621      * 
622      * @see org.apache.jetspeed.page.document.DocumentHandler#getDocument(java.lang.String,
623      *      boolean)
624      * @param name
625      * @param fromCahe
626      *            Whether or not the Document should be pulled from the cache.
627      * @return @throws
628      *         DocumentNotFoundException
629      */
630     public Document getDocument( String name, boolean fromCache ) throws DocumentNotFoundException, NodeException
631     {
632         Document document = null;
633         if (fromCache)
634         {
635             Object obj = fileCache.getDocument(name);
636             document = (Document) obj;
637             if (document == null)
638             {
639                 document = (Document) unmarshallDocument(expectedReturnType, name, documentType);
640                 addToCache(name, document);
641             }
642         }
643         else
644         {
645             document = (Document) unmarshallDocument(expectedReturnType, name, documentType);
646         }
647 
648         return document;
649     }
650 
651     /***
652      * <p>
653      * addToCache
654      * </p>
655      * 
656      * @param path
657      * @param objectToCache
658      */
659     protected void addToCache( String path, Object objectToCache )
660     {
661         synchronized (fileCache)
662         {
663             // store the document in the hash and reference it to the
664             // watcher
665             try
666             {
667                 fileCache.put(path, objectToCache, this.documentRootDir);
668 
669             }
670             catch (java.io.IOException e)
671             {
672                 log.error("Error putting document: " + e);
673                 IllegalStateException ise = new IllegalStateException("Error storing Document in the FileCache: "
674                         + e.toString());
675                 ise.initCause(e);
676             }
677         }
678     }
679 
680     /***
681      * <p>
682      * refresh
683      * </p>
684      * 
685      * @see org.apache.jetspeed.cache.file.FileCacheEventListener#refresh(org.apache.jetspeed.cache.file.FileCacheEntry)
686      * @param entry
687      * @throws Exception
688      */
689     public void refresh( FileCacheEntry entry ) throws Exception
690     {
691         log.debug("Entry is refreshing: " + entry.getFile().getName());
692 
693         if (entry.getDocument() instanceof Document && ((Document) entry.getDocument()).getPath().endsWith(documentType))
694         {
695             Document document = (Document) entry.getDocument();
696             Document freshDoc = getDocument(document.getPath(), false);
697             Node parent = ((AbstractNode)document).getParent(false);
698  
699             freshDoc.setParent(parent);
700             if(parent instanceof FolderImpl)
701             {
702                 FolderImpl folder = (FolderImpl) parent;
703                 folder.getAllNodes().add(freshDoc);
704             }
705             
706             freshDoc.setPath(document.getPath());
707             entry.setDocument(freshDoc);            
708         }
709 
710     }
711 
712     /***
713      * <p>
714      * evict
715      * </p>
716      * 
717      * @see org.apache.jetspeed.cache.file.FileCacheEventListener#evict(org.apache.jetspeed.cache.file.FileCacheEntry)
718      * @param entry
719      * @throws Exception
720      */
721     public void evict( FileCacheEntry entry ) throws Exception
722     {
723         // TODO Auto-generated method stub
724 
725     }
726 
727     /***
728      * <p>
729      * getType
730      * </p>
731      *
732      * @see org.apache.jetspeed.page.document.DocumentHandler#getType()
733      * @return
734      */
735     public String getType()
736     {
737         return documentType;
738     }
739 
740     /***
741      * <p>
742      * getHandlerFactory
743      * </p>
744      *
745      * @see org.apache.jetspeed.page.document.DocumentHandler#getHandlerFactory()
746      * @return
747      */
748     public DocumentHandlerFactory getHandlerFactory()
749     {
750         return handlerFactory;
751     }
752 
753     /***
754      * <p>
755      * setHandlerFactory
756      * </p>
757      *
758      * @see org.apache.jetspeed.page.document.DocumentHandler#setHandlerFactory(org.apache.jetspeed.page.document.DocumentHandlerFactory)
759      * @param factory
760      */
761     public void setHandlerFactory(DocumentHandlerFactory factory)
762     {
763         this.handlerFactory = factory;
764     }
765 
766 }