/* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 2000-2001 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" and * "Apache Jetspeed" must not be used to endorse or promote products * derived from this software without prior written permission. For * written permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" or * "Apache Jetspeed", nor may "Apache" appear in their name, without * prior written permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * . */ package org.apache.jetspeed.services.registry; // Jetspeed stuff import org.apache.jetspeed.om.registry.Registry; import org.apache.jetspeed.om.registry.RegistryEntry; import org.apache.jetspeed.om.registry.RegistryException; import org.apache.jetspeed.om.registry.base.BaseRegistry; import org.apache.jetspeed.om.registry.base.LocalRegistry; //turbine stuff import org.apache.turbine.util.Log; import org.apache.turbine.services.InitializationException; import org.apache.turbine.services.TurbineBaseService; import org.apache.turbine.services.TurbineServices; import org.apache.turbine.services.servlet.TurbineServlet; import org.apache.turbine.services.servlet.ServletService; import org.apache.turbine.services.resources.ResourceService; // castor marshalling import org.exolab.castor.mapping.Mapping; import org.exolab.castor.xml.Unmarshaller; import org.exolab.castor.xml.Marshaller; import org.xml.sax.InputSource; import org.apache.xml.serialize.Serializer; import org.apache.xml.serialize.XMLSerializer; import org.apache.xml.serialize.OutputFormat; import org.w3c.dom.Document; import org.w3c.dom.Node; //java stuff import java.io.File; import java.io.FileFilter; import java.io.FileReader; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.Iterator; import java.util.Vector; import javax.servlet.ServletConfig; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; /** *

This is an implementation of the RegistryService * based on the Castor XML serialization mechanisms

*

This registry aggregates multiple RegistryFragment to store the regsistry * entries

* *

This service expects the following properties to be set for correct operation: *

*
directory
The directory where the Registry will look for * fragment files
*
extension
The extension used for identifying the registry fragment * files. Default .xreg
*
mapping
the Castor object mapping file path
*
registries
a comma separated list of registry names to load * from this file
*
refreshRate
Optional. The manager will check every * refreshRate seconds if the config has changed and if true will refresh * all the registries. A value of 0 or negative will disable the * automatic refresh operation. Default: 300 (5 minutes)
*
verbose
Optional. Control the amount of debug output. The bigger * the more output, you've been warned ! Default: 0
*
*

* * @author Raphaël Luta * @author Santiago Gala * @version $Id: CastorRegistryService.java,v 1.31 2003/04/16 14:52:57 morciuch Exp $ */ public class CastorRegistryService extends TurbineBaseService implements RegistryService, FileRegistry { public static final int DEFAULT_VERBOSE = 0; public static final int DEFAULT_REFRESH = 300; public static final String DEFAULT_EXTENSION = ".xreg"; public static final String DEFAULT_MAPPING = "${webapp}/WEB-INF/conf/mapping.xml"; /** controls amount of debug output, the bigger the more output will be generated */ private int verbose = DEFAULT_VERBOSE; /** regsitry type keyed list of entries */ private Hashtable registries = new Hashtable(); /** The Castor generated RegsitryFragment objects */ private Hashtable fragments = new Hashtable(); /** The list of default fragments stores for newly created objects */ private Hashtable defaults = new Hashtable(); /** Associates entries with their fragments name for quick lookup */ private Hashtable entryIndex = new Hashtable(); /** the Watcher object which monitors the regsitry directory */ private RegistryWatcher watcher = null; /** the Castor mapping file name */ private Mapping mapping = null; /** the output format for pretty printing when saving registries */ private OutputFormat format = null; /** map of files being processed */ Map beingProcessed = new HashMap(); /** the base regsitry directory */ private String directory = null; /** the extension for registry files */ private String extension = null; /** * Returns a Registry object for further manipulation * * @param regName the name of the registry to fetch * @return a Registry object if found by the manager or null */ public Registry get(String regName) { return (Registry) registries.get(regName); } /** * List all the registry currently available to this service * * @return an Enumeration of registry names. */ public Enumeration getNames() { return registries.keys(); } /** * Creates a new RegistryEntry instance compatible with the current * Registry instance implementation * * @param regName the name of the registry to use * @return the newly created RegistryEntry */ public RegistryEntry createEntry(String regName) { RegistryEntry entry = null; Registry registry = (Registry) registries.get(regName); if (registry != null) { entry = registry.createEntry(); } return entry; } /** * Returns a RegistryEntry from the named Registry. * This is a convenience wrapper around {@link * org.apache.jetspeed.om.registry.Registry#getEntry } * * @param regName the name of the registry * @param entryName the name of the entry to retrieve from the * registry * @return a RegistryEntry object if the key is found or null */ public RegistryEntry getEntry(String regName, String entryName) { try { waitForInit(regName, entryName); } catch (InterruptedException e) { Log.info("wait for initialise of " + entryName + " in " + regName + " interupted", e); } try { return ((Registry) registries.get(regName)).getEntry(entryName); } catch (RegistryException e) { if (Log.getLogger().isInfoEnabled()) { Log.info("RegistryService: Failed to retrieve " + entryName + " from " + regName , e); } } catch (NullPointerException e) { Log.error("RegistryService: " + regName + " registry is not known "); Log.error(e); } return null; } /** * method to waitForInit of registry * @param regName registry name * @param entryName entry name * @throws InterruptedException if the thread is interrupted */ private void waitForInit(String regName, String entryName) throws InterruptedException { RegistryEntry entry = null; Registry reg = null; while (entry == null && beingProcessed.size() > 0) { try { reg = (Registry) registries.get(regName); if (reg != null) { entry = reg.getEntry(entryName); } } catch (RegistryException e) { if ((verbose > 2) && Log.getLogger().isDebugEnabled()) { Log.debug("waiting for initialisation of " + entryName + " in " + regName, e); } } Thread.sleep(500); } } /** * Add a new RegistryEntry in the named Registry. * This is a convenience wrapper around {@link * org.apache.jetspeed.om.registry.Registry#addEntry } * * @param regName the name of the registry * @param entry the Registry entry to add * @exception Sends a RegistryException if the manager can't add * the provided entry */ public void addEntry(String regName, RegistryEntry entry) throws RegistryException { if (entry == null) { return; } LocalRegistry registry = (LocalRegistry) registries.get(regName); if (registry != null) { String fragmentName = (String) entryIndex.get(entry.getName()); if (fragmentName == null) { // either the entry was deleted or it does not exist // in both cases, use the default fragment fragmentName = (String) defaults.get(regName); } RegistryFragment fragment = (RegistryFragment) fragments.get(fragmentName); //Fragment can be (and sometimes is, but should not be) null if (fragment == null) { fragment = new RegistryFragment(); fragment.put(regName, new Vector()); fragments.put(fragmentName, fragment); } else { Vector vectRegistry = (Vector) fragment.get(regName); if (vectRegistry == null) { fragment.put(regName, new Vector()); } } synchronized (entryIndex) { if (registry.hasEntry(entry.getName())) { fragment.setEntry(regName, entry); registry.setLocalEntry(entry); } else { fragment.addEntry(regName, entry); registry.addLocalEntry(entry); } entryIndex.put(entry.getName(), fragmentName); // mark this fragment so that it's persisted next time // the registry watcher is running fragment.setDirty(true); } } } /** * Deletes a RegistryEntry from the named Registry * This is a convenience wrapper around {@link * org.apache.jetspeed.om.registry.Registry#removeEntry } * * @param regName the name of the registry * @param entryName the name of the entry to remove */ public void removeEntry(String regName, String entryName) { if (entryName == null) { return; } LocalRegistry registry = (LocalRegistry) registries.get(regName); if (registry != null) { String fragmentName = (String) entryIndex.get(entryName); if (fragmentName != null) { RegistryFragment fragment = (RegistryFragment) fragments.get(fragmentName); synchronized (entryIndex) { fragment.removeEntry(regName, entryName); entryIndex.remove(entryName); // mark this fragment so that it's persisted next time // the registry watcher is running fragment.setDirty(true); } } // the entry is physically removed, remove the dangling reference registry.removeLocalEntry(entryName); } } /** * This is the early initialization method called by the * Turbine Service framework */ public synchronized void init(ServletConfig conf) throws InitializationException { //Ensure that the servlet service is initialized TurbineServices.getInstance().initService(ServletService.SERVICE_NAME, conf); ResourceService serviceConf = ((TurbineServices) TurbineServices.getInstance()) .getResources(RegistryService.SERVICE_NAME); String mapFile = null; Vector names = new Vector(); int refreshRate = 0; // read the configuration keys try { directory = serviceConf.getString("directory"); mapFile = serviceConf.getString("mapping", DEFAULT_MAPPING); extension = serviceConf.getString("extension", DEFAULT_EXTENSION); refreshRate = serviceConf.getInt("refreshRate", DEFAULT_REFRESH); verbose = serviceConf.getInt("verbose", DEFAULT_VERBOSE); mapFile = TurbineServlet.getRealPath(mapFile); directory = TurbineServlet.getRealPath(directory); } catch (Throwable t) { throw new InitializationException("Unable to initialize CastorRegistryService, missing config keys"); } // build the map of default fragments, eahc registry must be associated // with at least one fragment try { ResourceService defaults = serviceConf.getResources("default"); Iterator i = defaults.getKeys(); while (i.hasNext()) { String name = (String) i.next(); String fragmentFileName = defaults.getString(name); String absFileName = new File(directory, fragmentFileName + extension).getAbsolutePath(); // add this name in the list of available registries names.add(name); // store the default file mapping this.defaults.put(name, absFileName); } } catch (Exception e) { Log.error("RegistryService: Registry init error", e); throw new InitializationException("Unable to initialize CastorRegistryService, invalid registries definition"); } // create the serializer output format this.format = new OutputFormat(); format.setIndenting(true); format.setIndent(4); // test the mapping file and create the mapping object if (mapFile != null) { File map = new File(mapFile); if (map.exists() && map.isFile() && map.canRead()) { try { mapping = new Mapping(); InputSource is = new InputSource(new FileReader(map)); is.setSystemId(mapFile); mapping.loadMapping(is); } catch (Exception e) { Log.error("RegistryService: Error in mapping creation", e); throw new InitializationException("Error in mapping", e); } } else { throw new InitializationException("Mapping not found or not a file or unreadable: " + mapFile); } } // Set directory watcher if directory exists File base = new File(directory); File[] files = null; if (base.exists() && base.isDirectory() && base.canRead()) { this.watcher = new RegistryWatcher(); this.watcher.setSubscriber(this); this.watcher.setFilter(new ExtFileFilter(extension)); if (refreshRate == 0) { this.watcher.setDone(); } else { this.watcher.setRefreshRate(refreshRate); } // changing the base will trigger a synchronous loading of the fragments this.watcher.changeBase(base); } //Mark that we are done setInit(true); // load the registries Enumeration en = names.elements(); while (en.hasMoreElements()) { String name = (String) en.nextElement(); Registry registry = (Registry) registries.get(name); if (registry == null) { String registryClass = null; try { registryClass = "org.apache.jetspeed.om.registry.base.Base" + name + "Registry"; registry = (Registry) Class.forName(registryClass).newInstance(); } catch (Exception e) { if (Log.getLogger().isWarnEnabled()) { Log.warn("RegistryService: Class " + registryClass + " not found, reverting to default Registry"); } registry = new BaseRegistry(); } registries.put(name, registry); } refresh(name); } // Start the directory watcher thread and rely on its refresh process // to completely load all registries if (this.watcher != null) { this.watcher.start(); } if (Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: early init()....end!, this.getInit()= " + getInit()); } } /** Late init method from Turbine Service model */ public void init() throws InitializationException { if (Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: Late init called"); } while (!getInit()) { //Not yet... try { Thread.sleep(500); if ((verbose > 2) && Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: Waiting for init of Registry..."); } } catch (InterruptedException ie) { Log.error(ie); } } if (Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: We are done"); } } /** * This is the shutdown method called by the * Turbine Service framework */ public void shutdown() { this.watcher.setDone(); Iterator i = fragments.keySet().iterator(); while (i.hasNext()) { saveFragment((String) i.next()); } } // FileRegistry interface /** Refresh the state of the registry implementation. Should be called * whenever the underlying fragments are modified */ public void refresh() { synchronized (watcher) { Enumeration en = getNames(); while (en.hasMoreElements()) { refresh((String) en.nextElement()); } } } /** * @return a Map of all fragments keyed by file names */ public Map getFragmentMap() { return (Map) fragments.clone(); } /** * Load and unmarshal a RegistryFragment from the file * @param file the absolute file path storing this fragment */ public void loadFragment(String file) { try { beingProcessed.put(file, file); (new FragmentLoaderThread(file)).start(); } catch (Throwable t) { Log.error("RegistryService: Could not unmarshal: " + file, t); } } /** * Read and unmarshal a fragment in memory * @param name the name of this fragment * @param reader the reader to use for creating this fragment * @param persistent whether this fragment should be persisted on disk in * the registry */ public void createFragment(String name, Reader reader, boolean persistent) { String file = null; try { file = new File(directory, name + extension).getCanonicalPath(); Unmarshaller unmarshaller = new Unmarshaller(this.mapping); RegistryFragment fragment = (RegistryFragment) unmarshaller.unmarshal(reader); fragment.setChanged(true); updateFragment(file, fragment); if (persistent) { saveFragment(file); } } catch (Throwable t) { Log.error("RegistryService: Could not create fragment: " + file, t); } finally { try { reader.close(); } catch (Exception e) { Log.error(e); // At least log the exception. } } } /** * Marshal and save a RegistryFragment to disk * @param file the absolute file path storing this fragment */ public void saveFragment(String file) { OutputStreamWriter writer = null; String encoding = new String("UTF-8"); RegistryFragment fragment = (RegistryFragment) fragments.get(file); if (fragment != null) { try { writer = new OutputStreamWriter(new FileOutputStream(file), encoding); format.setEncoding(encoding); Serializer serializer = new XMLSerializer(writer, format); Marshaller marshaller = new Marshaller(serializer.asDocumentHandler()); marshaller.setMapping(this.mapping); marshaller.marshal(fragment); } catch (Throwable t) { Log.error("RegistryService: Could not marshal: " + file, t); } finally { try { writer.close(); } catch (Exception e) { Log.error(e); // At least log the exception. } } } } /** * Remove a fragment from storage * @param file the absolute file path storing this fragment */ public void removeFragment(String file) { RegistryFragment fragment = (RegistryFragment) fragments.get(file); if (fragment != null) { synchronized (entryIndex) { // clear the entry index Iterator i = entryIndex.keySet().iterator(); while (i.hasNext()) { if (file.equals(entryIndex.get(i.next()))) { i.remove(); } } // make sure the keys & entries are freed for this fragment // only the entries not replaced by the next registry refresh will // stay in memory fragment.clear(); // remove the actual fragment from memory fragments.remove(file); } } } // Implementation specific methods /** * Updates a fragment in storage and the associated entryIndex */ protected void updateFragment(String name, RegistryFragment fragment) { synchronized (entryIndex) { // remove the old keys Iterator i = entryIndex.keySet().iterator(); while (i.hasNext()) { if (name.equals(entryIndex.get(i.next()))) { i.remove(); } } // store the new fragment fragments.put(name, fragment); // recreate the index entries (only this fragment) Enumeration enum = fragment.keys(); while (enum.hasMoreElements()) { String strReg = (String) enum.nextElement(); Vector v = fragment.getEntries(strReg); for (int counter = 0; counter < v.size(); counter++) { RegistryEntry str = (RegistryEntry) v.elementAt(counter); entryIndex.put(str.getName(), name); } } } } /** * Scan all the registry fragments for new entries relevant to * this registry and update its definition. * * @param regName the name of the Registry to refresh */ protected void refresh(String regName) { if (Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: Updating the " + regName + " registry"); } int count = 0; int counDeleted = 0; LocalRegistry registry = (LocalRegistry) get(regName); Vector toDelete = new Vector(); Iterator i = registry.listEntryNames(); while (i.hasNext()) { toDelete.add(i.next()); } if (registry == null) { Log.error("RegistryService: Null " + name + " registry in refresh"); return; } // for each fragment... Enumeration en = fragments.keys(); while (en.hasMoreElements()) { String location = (String) en.nextElement(); RegistryFragment fragment = (RegistryFragment) fragments.get(location); int fragCount = 0; if (!fragment.hasChanged()) { if ((verbose > 2) && Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: Skipping fragment " + location); } //remove this fragment entries from the delete list Vector entries = fragment.getEntries(regName); i = entries.iterator(); while (i.hasNext()) { toDelete.remove(((RegistryEntry) i.next()).getName()); } continue; } //the fragment has some changes, iterate over its entries... Vector entries = fragment.getEntries(regName); //... if it has entries related to this regsistry, if (entries != null) { // for all these entries Enumeration en2 = entries.elements(); while (en2.hasMoreElements()) { RegistryEntry entry = (RegistryEntry) en2.nextElement(); // update or add the entry in the registry try { if (registry.hasEntry(entry.getName())) { if (registry.getEntry(entry.getName()).equals(entry)) { if ((verbose > 2) && Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: No changes to entry " + entry.getName()); } } else { if ((verbose > 1) && Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: Updating entry " + entry.getName() + " of class " + entry.getClass() + " to registry " + name); } registry.setLocalEntry(entry); // Initialize the entry index this.entryIndex.put(entry.getName(), location); ++fragCount; } } else { registry.addLocalEntry(entry); // Initialize the entry index this.entryIndex.put(entry.getName(), location); ++fragCount; if ((verbose > 1) && Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: Adding entry " + entry.getName() + " of class " + entry.getClass() + " to registry " + name); } } } catch (RegistryException e) { Log.error("RegistryService: RegistryException while adding " + entry.getName() + "from " + location, e); } //remove this entry from the delete list toDelete.remove(entry.getName()); } } count += fragCount; } //now delete the entries not found in any fragment i = toDelete.iterator(); while (i.hasNext()) { String entryName = (String) i.next(); if ((verbose > 1) && Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: removing entry " + entryName); } registry.removeLocalEntry(entryName); } if ((verbose > 1) && Log.getLogger().isDebugEnabled()) { Log.debug("RegistryService: Merged " + count + " entries and deleted " + toDelete.size() + " in " + name); } } /** FileFilter implementing a file extension based filter */ class ExtFileFilter implements FileFilter { private String extension = null; ExtFileFilter(String extension) { this.extension = extension; } public boolean accept(File f) { return f.toString().endsWith(extension); } } class FragmentLoaderThread extends Thread { private String fileNm; public void run() { try { DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dbfactory.newDocumentBuilder(); Document d = builder.parse(new File(fileNm)); Unmarshaller unmarshaller = new Unmarshaller(mapping); unmarshaller.setValidation(false); RegistryFragment fragment = (RegistryFragment) unmarshaller.unmarshal((Node) d); //mark this fragment as changed fragment.setChanged(true); // if we get here, we successfully loaded the new fragment updateFragment(fileNm, fragment); beingProcessed.remove(fileNm); } catch(Throwable t) { Log.error("RegistryService: Could not unmarshal: " + fileNm, t); } } public FragmentLoaderThread (String fileNm) { this.fileNm = fileNm; } } }