1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.jetspeed.services.registry;
18
19
20 import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
21 import org.apache.jetspeed.services.logging.JetspeedLogger;
22 import org.apache.jetspeed.om.registry.Registry;
23 import org.apache.jetspeed.om.registry.RegistryEntry;
24 import org.apache.jetspeed.om.registry.RegistryException;
25 import org.apache.jetspeed.om.registry.base.BaseRegistry;
26 import org.apache.jetspeed.om.registry.base.LocalRegistry;
27
28
29 import org.apache.turbine.services.InitializationException;
30 import org.apache.turbine.services.TurbineBaseService;
31 import org.apache.turbine.services.TurbineServices;
32 import org.apache.turbine.services.servlet.TurbineServlet;
33 import org.apache.turbine.services.servlet.ServletService;
34 import org.apache.turbine.services.resources.ResourceService;
35
36
37 import org.exolab.castor.mapping.Mapping;
38 import org.exolab.castor.xml.Unmarshaller;
39 import org.exolab.castor.xml.Marshaller;
40 import org.xml.sax.InputSource;
41 import org.apache.xml.serialize.Serializer;
42 import org.apache.xml.serialize.XMLSerializer;
43 import org.apache.xml.serialize.OutputFormat;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Node;
46
47
48 import java.io.File;
49 import java.io.FileFilter;
50 import java.io.FileReader;
51 import java.io.FileOutputStream;
52 import java.io.OutputStreamWriter;
53 import java.io.Reader;
54 import java.util.Enumeration;
55 import java.util.Hashtable;
56 import java.util.Map;
57 import java.util.Iterator;
58 import java.util.Vector;
59 import javax.servlet.ServletConfig;
60 import javax.xml.parsers.DocumentBuilder;
61 import javax.xml.parsers.DocumentBuilderFactory;
62
63 /***
64 * <p>This is an implementation of the <code>RegistryService</code>
65 * based on the Castor XML serialization mechanisms</p>
66 * <p>This registry aggregates multiple RegistryFragment to store the regsistry
67 * entries</p>
68 *
69 * <p>This service expects the following properties to be set for correct operation:
70 * <dl>
71 * <dt>directory</dt><dd>The directory where the Registry will look for
72 * fragment files</dd>
73 * <dt>extension</dt><dd>The extension used for identifying the registry fragment
74 * files. Default .xreg</dd>
75 * <dt>mapping</dt><dd>the Castor object mapping file path</dd>
76 * <dt>registries</dt><dd>a comma separated list of registry names to load
77 * from this file</dd>
78 * <dt>refreshRate</dt><dd>Optional. The manager will check every
79 * refreshRate seconds if the config has changed and if true will refresh
80 * all the registries. A value of 0 or negative will disable the
81 * automatic refresh operation. Default: 300 (5 minutes)</dd>
82 * </dl>
83 * </p>
84 *
85 * @author <a href="mailto:raphael@apache.org">Raphaël Luta</a>
86 * @author <a href="mailto:sgala@apache.org">Santiago Gala</a>
87 * @version $Id: CastorRegistryService.java,v 1.37 2004/03/31 00:23:02 jford Exp $
88 */
89 public class CastorRegistryService
90 extends TurbineBaseService
91 implements RegistryService, FileRegistry
92 {
93 /***
94 * Static initialization of the logger for this class
95 */
96 private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(CastorRegistryService.class.getName());
97
98 public static final int DEFAULT_REFRESH = 300;
99 public static final String DEFAULT_EXTENSION = ".xreg";
100 public static final String DEFAULT_MAPPING = "${webapp}/WEB-INF/conf/mapping.xml";
101
102 /*** regsitry type keyed list of entries */
103 private Hashtable registries = new Hashtable();
104
105 /*** The Castor generated RegsitryFragment objects */
106 private Hashtable fragments = new Hashtable();
107
108 /*** The list of default fragments stores for newly created objects */
109 private Hashtable defaults = new Hashtable();
110
111 /*** Associates entries with their fragments name for quick lookup */
112 private Hashtable entryIndex = new Hashtable();
113
114 /*** the Watcher object which monitors the regsitry directory */
115 private RegistryWatcher watcher = null;
116
117 /*** the Castor mapping file name */
118 private Mapping mapping = null;
119
120 /*** the output format for pretty printing when saving registries */
121 private OutputFormat format = null;
122
123 /*** the base regsitry directory */
124 private String directory = null;
125
126 /*** the extension for registry files */
127 private String extension = null;
128
129 /***
130 * Returns a Registry object for further manipulation
131 *
132 * @param regName the name of the registry to fetch
133 * @return a Registry object if found by the manager or null
134 */
135 public Registry get(String regName)
136 {
137 return (Registry) registries.get(regName);
138 }
139
140 /***
141 * List all the registry currently available to this service
142 *
143 * @return an Enumeration of registry names.
144 */
145 public Enumeration getNames()
146 {
147 return registries.keys();
148 }
149
150 /***
151 * Creates a new RegistryEntry instance compatible with the current
152 * Registry instance implementation
153 *
154 * @param regName the name of the registry to use
155 * @return the newly created RegistryEntry
156 */
157 public RegistryEntry createEntry(String regName)
158 {
159 RegistryEntry entry = null;
160 Registry registry = (Registry) registries.get(regName);
161
162 if (registry != null)
163 {
164 entry = registry.createEntry();
165 }
166
167 return entry;
168 }
169
170 /***
171 * Returns a RegistryEntry from the named Registry.
172 * This is a convenience wrapper around {@link
173 * org.apache.jetspeed.om.registry.Registry#getEntry }
174 *
175 * @param regName the name of the registry
176 * @param entryName the name of the entry to retrieve from the
177 * registry
178 * @return a RegistryEntry object if the key is found or null
179 */
180 public RegistryEntry getEntry(String regName, String entryName)
181 {
182 try
183 {
184 return ((Registry) registries.get(regName)).getEntry(entryName);
185 }
186 catch (RegistryException e)
187 {
188 if (logger.isInfoEnabled())
189 {
190 logger.info("RegistryService: Failed to retrieve " + entryName + " from " + regName);
191 }
192 }
193 catch (NullPointerException e)
194 {
195 logger.error("RegistryService: " + regName + " registry is not known ", e);
196 }
197
198 return null;
199 }
200
201 /***
202 * Add a new RegistryEntry in the named Registry.
203 * This is a convenience wrapper around {@link
204 * org.apache.jetspeed.om.registry.Registry#addEntry }
205 *
206 * @param regName the name of the registry
207 * @param entry the Registry entry to add
208 * @exception Sends a RegistryException if the manager can't add
209 * the provided entry
210 */
211 public void addEntry(String regName, RegistryEntry entry) throws RegistryException
212 {
213 if (entry == null)
214 {
215 return;
216 }
217
218 LocalRegistry registry = (LocalRegistry) registries.get(regName);
219
220 if (registry != null)
221 {
222 String fragmentName = (String) entryIndex.get(entry.getName());
223
224 if (fragmentName == null)
225 {
226
227
228 fragmentName = (String) defaults.get(regName);
229 }
230
231 RegistryFragment fragment = (RegistryFragment) fragments.get(fragmentName);
232
233
234 if (fragment == null)
235 {
236 fragment = new RegistryFragment();
237 fragment.put(regName, new Vector());
238 fragments.put(fragmentName, fragment);
239 }
240 else
241 {
242 Vector vectRegistry = (Vector) fragment.get(regName);
243 if (vectRegistry == null)
244 {
245 fragment.put(regName, new Vector());
246 }
247 }
248
249 synchronized (entryIndex)
250 {
251 if (registry.hasEntry(entry.getName()))
252 {
253 fragment.setEntry(regName, entry);
254 registry.setLocalEntry(entry);
255 }
256 else
257 {
258 fragment.addEntry(regName, entry);
259 registry.addLocalEntry(entry);
260 }
261
262 entryIndex.put(entry.getName(), fragmentName);
263
264
265 fragment.setDirty(true);
266 }
267 }
268 }
269
270 /***
271 * Deletes a RegistryEntry from the named Registry
272 * This is a convenience wrapper around {@link
273 * org.apache.jetspeed.om.registry.Registry#removeEntry }
274 *
275 * @param regName the name of the registry
276 * @param entryName the name of the entry to remove
277 */
278 public void removeEntry(String regName, String entryName)
279 {
280 if (entryName == null)
281 {
282 return;
283 }
284
285 LocalRegistry registry = (LocalRegistry) registries.get(regName);
286
287 if (registry != null)
288 {
289 String fragmentName = (String) entryIndex.get(entryName);
290
291 if (fragmentName != null)
292 {
293 RegistryFragment fragment = (RegistryFragment) fragments.get(fragmentName);
294
295 synchronized (entryIndex)
296 {
297 fragment.removeEntry(regName, entryName);
298 entryIndex.remove(entryName);
299
300
301
302 fragment.setDirty(true);
303 }
304 }
305
306
307 registry.removeLocalEntry(entryName);
308 }
309 }
310
311 /***
312 * This is the early initialization method called by the
313 * Turbine <code>Service</code> framework
314 */
315 public synchronized void init(ServletConfig conf) throws InitializationException
316 {
317
318
319 TurbineServices.getInstance().initService(ServletService.SERVICE_NAME, conf);
320
321 ResourceService serviceConf = ((TurbineServices) TurbineServices.getInstance())
322 .getResources(RegistryService.SERVICE_NAME);
323 String mapFile = null;
324 Vector names = new Vector();
325 int refreshRate = 0;
326
327
328 try
329 {
330 directory = serviceConf.getString("directory");
331 mapFile = serviceConf.getString("mapping", DEFAULT_MAPPING);
332 extension = serviceConf.getString("extension", DEFAULT_EXTENSION);
333 refreshRate = serviceConf.getInt("refreshRate", DEFAULT_REFRESH);
334
335 mapFile = TurbineServlet.getRealPath(mapFile);
336 directory = TurbineServlet.getRealPath(directory);
337 }
338 catch (Throwable t)
339 {
340 throw new InitializationException("Unable to initialize CastorRegistryService, missing config keys");
341 }
342
343
344
345 try
346 {
347 ResourceService defaults = serviceConf.getResources("default");
348 Iterator i = defaults.getKeys();
349 while (i.hasNext())
350 {
351 String name = (String) i.next();
352 String fragmentFileName = defaults.getString(name);
353
354 String absFileName = new File(directory, fragmentFileName + extension).getCanonicalPath();
355
356 names.add(name);
357
358
359 this.defaults.put(name, absFileName);
360 }
361 }
362 catch (Exception e)
363 {
364 logger.error("RegistryService: Registry init error", e);
365 throw new InitializationException("Unable to initialize CastorRegistryService, invalid registries definition");
366 }
367
368
369 this.format = new OutputFormat();
370 format.setIndenting(true);
371 format.setIndent(4);
372 format.setLineWidth(0);
373
374
375
376 if (mapFile != null)
377 {
378 File map = new File(mapFile);
379 if (map.exists() && map.isFile() && map.canRead())
380 {
381 try
382 {
383 mapping = new Mapping();
384 InputSource is = new InputSource(new FileReader(map));
385 is.setSystemId(mapFile);
386 mapping.loadMapping(is);
387 }
388 catch (Exception e)
389 {
390 logger.error("RegistryService: Error in mapping creation", e);
391 throw new InitializationException("Error in mapping", e);
392 }
393 }
394 else
395 {
396 throw new InitializationException("Mapping not found or not a file or unreadable: " + mapFile);
397 }
398 }
399
400
401 File base = new File(directory);
402 File[] files = null;
403
404 if (base.exists() && base.isDirectory() && base.canRead())
405 {
406 this.watcher = new RegistryWatcher();
407 this.watcher.setSubscriber(this);
408 this.watcher.setFilter(new ExtFileFilter(extension));
409 if (refreshRate == 0)
410 {
411 this.watcher.setDone();
412 }
413 else
414 {
415 this.watcher.setRefreshRate(refreshRate);
416 }
417
418 this.watcher.changeBase(base);
419 }
420
421
422 setInit(true);
423
424
425
426 Enumeration en = names.elements();
427
428 while (en.hasMoreElements())
429 {
430 String name = (String) en.nextElement();
431 Registry registry = (Registry) registries.get(name);
432
433 if (registry == null)
434 {
435 String registryClass = null;
436 try
437 {
438 registryClass =
439 "org.apache.jetspeed.om.registry.base.Base"
440 + name
441 + "Registry";
442
443 registry = (Registry) Class.forName(registryClass).newInstance();
444 }
445 catch (Exception e)
446 {
447 if (logger.isWarnEnabled())
448 {
449 logger.warn("RegistryService: Class "
450 + registryClass
451 + " not found, reverting to default Registry");
452 }
453 registry = new BaseRegistry();
454 }
455
456 registries.put(name, registry);
457 }
458
459 refresh(name);
460 }
461
462
463
464 if (this.watcher != null)
465 {
466 this.watcher.start();
467 }
468
469 if (logger.isDebugEnabled())
470 {
471 logger.debug("RegistryService: early init()....end!, this.getInit()= " + getInit());
472 }
473
474 }
475
476
477 /*** Late init method from Turbine Service model */
478 public void init() throws InitializationException
479 {
480 if (logger.isDebugEnabled())
481 {
482 logger.debug("RegistryService: Late init called");
483 }
484
485 while (!getInit())
486 {
487
488 try
489 {
490 Thread.sleep(500);
491 if (logger.isDebugEnabled())
492 {
493 logger.debug("RegistryService: Waiting for init of Registry...");
494 }
495 }
496 catch (InterruptedException ie)
497 {
498 logger.error("Exception", ie);
499 }
500 }
501
502 if (logger.isDebugEnabled())
503 {
504 logger.debug("RegistryService: We are done");
505 }
506 }
507
508 /***
509 * This is the shutdown method called by the
510 * Turbine <code>Service</code> framework
511 */
512 public void shutdown()
513 {
514 this.watcher.setDone();
515
516 Iterator i = fragments.keySet().iterator();
517 while (i.hasNext())
518 {
519 saveFragment((String) i.next());
520 }
521 }
522
523
524
525 /*** Refresh the state of the registry implementation. Should be called
526 * whenever the underlying fragments are modified
527 */
528 public void refresh()
529 {
530 synchronized (watcher)
531 {
532 Enumeration en = getNames();
533 while (en.hasMoreElements())
534 {
535 refresh((String) en.nextElement());
536 }
537 }
538 }
539
540 /***
541 * @return a Map of all fragments keyed by file names
542 */
543 public Map getFragmentMap()
544 {
545 return (Map) fragments.clone();
546 }
547
548 /***
549 * Load and unmarshal a RegistryFragment from the file
550 * @param file the absolute file path storing this fragment
551 */
552 public void loadFragment(String file)
553 {
554 try
555 {
556 DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
557 DocumentBuilder builder = dbfactory.newDocumentBuilder();
558
559 Document d = builder.parse(new File(file));
560
561 Unmarshaller unmarshaller = new Unmarshaller(this.mapping);
562 RegistryFragment fragment = (RegistryFragment) unmarshaller.unmarshal((Node) d);
563
564
565 fragment.setChanged(true);
566
567
568 updateFragment(file, fragment);
569
570 }
571 catch (Throwable t)
572 {
573 logger.error("RegistryService: Could not unmarshal: " + file, t);
574 }
575
576 }
577
578 /***
579 * Read and unmarshal a fragment in memory
580 * @param name the name of this fragment
581 * @param reader the reader to use for creating this fragment
582 * @param persistent whether this fragment should be persisted on disk in
583 * the registry
584 */
585 public void createFragment(String name, Reader reader, boolean persistent)
586 {
587 String file = null;
588
589 try
590 {
591 synchronized(watcher)
592 {
593 file = new File(directory, name + extension).getCanonicalPath();
594
595 Unmarshaller unmarshaller = new Unmarshaller(this.mapping);
596 RegistryFragment fragment = (RegistryFragment) unmarshaller.unmarshal(reader);
597
598 fragment.setChanged(true);
599
600 updateFragment(file, fragment);
601
602 if (persistent)
603 {
604 saveFragment(file);
605 }
606 }
607 }
608 catch (Throwable t)
609 {
610 logger.error("RegistryService: Could not create fragment: " + file, t);
611 }
612 finally
613 {
614 try
615 {
616 reader.close();
617 }
618 catch (Exception e)
619 {
620 logger.error("Exception", e);
621 }
622 }
623 }
624
625 /***
626 * Marshal and save a RegistryFragment to disk
627 * @param file the absolute file path storing this fragment
628 */
629 public void saveFragment(String file)
630 {
631 OutputStreamWriter writer = null;
632 FileOutputStream fos = null;
633 String encoding = new String("UTF-8");
634 RegistryFragment fragment = (RegistryFragment) fragments.get(file);
635
636 if (fragment != null)
637 {
638 try
639 {
640 fos = new FileOutputStream(file);
641 writer = new OutputStreamWriter(fos, encoding);
642 format.setEncoding(encoding);
643 Serializer serializer = new XMLSerializer(writer, format);
644 Marshaller marshaller = new Marshaller(serializer.asDocumentHandler());
645 marshaller.setMapping(this.mapping);
646 marshaller.marshal(fragment);
647 }
648 catch (Throwable t)
649 {
650 logger.error("RegistryService: Could not marshal: " + file, t);
651 }
652 finally
653 {
654 try
655 {
656 writer.close();
657 }
658 catch (Exception e)
659 {
660 logger.error("Exception", e);
661 }
662
663 try
664 {
665 fos.close();
666 }
667 catch (Exception e)
668 {
669 logger.error("Exception", e);
670 }
671 }
672 }
673 }
674
675 /***
676 * Remove a fragment from storage
677 * @param file the absolute file path storing this fragment
678 */
679 public void removeFragment(String file)
680 {
681 RegistryFragment fragment = (RegistryFragment) fragments.get(file);
682
683 if (fragment != null)
684 {
685 synchronized (entryIndex)
686 {
687
688 Iterator i = entryIndex.keySet().iterator();
689 while (i.hasNext())
690 {
691 if (file.equals(entryIndex.get(i.next())))
692 {
693 i.remove();
694 }
695 }
696
697
698
699
700 fragment.clear();
701
702 fragments.remove(file);
703 }
704 }
705 }
706
707
708
709 /***
710 * Updates a fragment in storage and the associated entryIndex
711 */
712 protected void updateFragment(String name, RegistryFragment fragment)
713 {
714 synchronized (entryIndex)
715 {
716
717 Iterator i = entryIndex.keySet().iterator();
718 while (i.hasNext())
719 {
720 if (name.equals(entryIndex.get(i.next())))
721 {
722 i.remove();
723 }
724 }
725
726
727 fragments.put(name, fragment);
728
729
730
731 Enumeration enum = fragment.keys();
732 while (enum.hasMoreElements())
733 {
734 String strReg = (String) enum.nextElement();
735 Vector v = fragment.getEntries(strReg);
736
737 for (int counter = 0; counter < v.size(); counter++)
738 {
739 RegistryEntry str = (RegistryEntry) v.elementAt(counter);
740 entryIndex.put(str.getName(), name);
741 }
742 }
743 }
744 }
745
746 /***
747 * Scan all the registry fragments for new entries relevant to
748 * this registry and update its definition.
749 *
750 * @param regName the name of the Registry to refresh
751 */
752 protected void refresh(String regName)
753 {
754
755 if (logger.isDebugEnabled())
756 {
757 logger.debug("RegistryService: Updating the " + regName + " registry");
758 }
759
760 int count = 0;
761 int counDeleted = 0;
762 LocalRegistry registry = (LocalRegistry) get(regName);
763
764 Vector toDelete = new Vector();
765 Iterator i = registry.listEntryNames();
766
767 while (i.hasNext())
768 {
769 toDelete.add(i.next());
770 }
771
772 if (registry == null)
773 {
774 logger.error("RegistryService: Null " + name + " registry in refresh");
775 return;
776 }
777
778
779 Enumeration en = fragments.keys();
780 while (en.hasMoreElements())
781 {
782 String location = (String) en.nextElement();
783 RegistryFragment fragment = (RegistryFragment) fragments.get(location);
784 int fragCount = 0;
785
786 if (!fragment.hasChanged())
787 {
788 if (logger.isDebugEnabled())
789 {
790 logger.debug("RegistryService: Skipping fragment " + location);
791 }
792
793
794 Vector entries = fragment.getEntries(regName);
795 i = entries.iterator();
796 while (i.hasNext())
797 {
798 toDelete.remove(((RegistryEntry) i.next()).getName());
799 }
800
801 continue;
802 }
803
804
805
806 Vector entries = fragment.getEntries(regName);
807
808
809 if (entries != null)
810 {
811
812 Enumeration en2 = entries.elements();
813 while (en2.hasMoreElements())
814 {
815 RegistryEntry entry = (RegistryEntry) en2.nextElement();
816
817 try
818 {
819 if (registry.hasEntry(entry.getName()))
820 {
821 if (registry.getEntry(entry.getName()).equals(entry))
822 {
823 if (logger.isDebugEnabled())
824 {
825 logger.debug("RegistryService: No changes to entry " + entry.getName());
826 }
827 }
828 else
829 {
830 if (logger.isDebugEnabled())
831 {
832 logger.debug("RegistryService: Updating entry " + entry.getName()
833 + " of class " + entry.getClass() + " to registry " + name);
834 }
835
836 registry.setLocalEntry(entry);
837
838 this.entryIndex.put(entry.getName(), location);
839 ++fragCount;
840 }
841 }
842 else
843 {
844 registry.addLocalEntry(entry);
845
846 this.entryIndex.put(entry.getName(), location);
847 ++fragCount;
848
849 if (logger.isDebugEnabled())
850 {
851 logger.debug("RegistryService: Adding entry " + entry.getName() + " of class "
852 + entry.getClass() + " to registry " + name);
853 }
854 }
855 }
856 catch (RegistryException e)
857 {
858 logger.error("RegistryService: RegistryException while adding " + entry.getName() + "from " + location, e);
859 }
860
861
862 toDelete.remove(entry.getName());
863 }
864 }
865
866 count += fragCount;
867 }
868
869
870 i = toDelete.iterator();
871 while (i.hasNext())
872 {
873 String entryName = (String) i.next();
874
875 if (logger.isDebugEnabled())
876 {
877 logger.debug("RegistryService: removing entry " + entryName);
878 }
879
880 registry.removeLocalEntry(entryName);
881 }
882
883
884 if (logger.isDebugEnabled())
885 {
886 logger.debug("RegistryService: Merged " + count + " entries and deleted " + toDelete.size() + " in " + name);
887 }
888 }
889
890
891 /*** FileFilter implementing a file extension based filter */
892 class ExtFileFilter implements FileFilter
893 {
894 private String extension = null;
895
896 ExtFileFilter(String extension)
897 {
898 this.extension = extension;
899 }
900
901 public boolean accept(File f)
902 {
903 return f.toString().endsWith(extension);
904 }
905 }
906
907 }