1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
135
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
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
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
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
220
221
222
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
263 if (qName.equals("menu"))
264 {
265 menuDepth--;
266 }
267
268
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
284 if ((menuDepth == 0) || !qName.equals("menu-element"))
285 {
286 handler.startElement(uri,localName, qName, atts);
287 }
288
289
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);
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
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
396
397
398
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);
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
447 handler.endElement(uri,localName,qName);
448
449
450
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
476
477
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
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
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
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
614 fileCache.remove(path);
615
616
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
668
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
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 }