/*
* Copyright 2000-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jetspeed.services.registry;
// Java classes
import java.io.Reader;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.servlet.ServletConfig;
import org.apache.jetspeed.om.registry.DBRegistry;
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;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.turbine.services.InitializationException;
import org.apache.turbine.services.TurbineBaseService;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.resources.ResourceService;
import org.apache.turbine.services.servlet.ServletService;
/**
* <p>
* This is an implementation of the <code>RegistryService</code> based on the
* Jetspeed Database Persistence Manager
* </p>
*
* @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
* @author <a href="mailto:susinha@cisco.com">Suchisubhra Sinha</a>
*/
public class DatabaseRegistryService extends TurbineBaseService implements
RegistryService, FileRegistry {
private static final JetspeedLogger logger = JetspeedLogFactoryService
.getLogger(CastorRegistryService.class.getName());
/** The name of this service */
public static final String SERVICE_NAME = "DatabaseRegistry";
public static final int DEFAULT_VERBOSE = 1;
/** regsitry type keyed list of entries */
private Hashtable registries = new Hashtable();
/** The list of default fragments stores for newly created objects */
private Hashtable defaults = new Hashtable();
/** The Castor generated RegsitryFragment objects */
private Hashtable fragments = 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 DatabaseRegistryWatcher watcher = null;
/** Assign the default poolname */
private final static String POOL_NAME = "database";
/**
* controls amount of debug output, the bigger the more output will be
* generated
*/
private int verbose = DEFAULT_VERBOSE;
/** Base class to implement */
private static Hashtable baseClass = new Hashtable();
/**
* 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 {
return ((Registry) registries.get(regName)).getEntry(entryName);
} catch (RegistryException e) {
if (logger.isInfoEnabled()) {
logger.info("RegistryService: Failed to retrieve " + entryName
+ " from " + regName);
}
} catch (NullPointerException e) {
logger.error("DatabaseRegistryService: " + regName
+ " registry is not known ");
logger.error(e);
}
return null;
}
/**
* 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
* <code>Service</code> framework
*/
public synchronized void init(ServletConfig conf)
throws InitializationException {
int refreshRate = 0;
Vector names = new Vector();
// Ensure that the servlet service is initialized
TurbineServices.getInstance()
.initService(ServletService.SERVICE_NAME, conf);
ResourceService serviceConf = ((TurbineServices) TurbineServices
.getInstance()).getResources(SERVICE_NAME);
// build the map of default fragments, eahc registry must be associated
// with at least one fragment
try {
refreshRate = serviceConf.getInt("refreshRate", DEFAULT_REFRESH);
ResourceService defaults = serviceConf.getResources("default");
Iterator i = defaults.getKeys();
while (i.hasNext()) {
String name = (String) i.next();
// add this name in the list of available registries
names.add(name);
try {
String registryClass = "org.apache.jetspeed.om.registry.database.BaseJetspeed"
+ name + "Peer";
baseClass.put(name, (DBRegistry) Class.forName(registryClass)
.newInstance());
} catch (Exception e) {
if (logger.isWarnEnabled()) {
logger
.warn("DatabaseRegistryService: Class " + name + " not found");
}
}
}
} catch (Throwable t) {
throw new InitializationException(
"Unable to initialize DatabaseRegistryService, missing config keys");
}
this.watcher = new DatabaseRegistryWatcher();
this.watcher.setSubscriber(this);
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(names);
// Mark that we are done
setInit(true);
// load the registries
Enumeration en = names.elements();
RegistryService localeService = (RegistryService) TurbineServices
.getInstance().getService(RegistryService.SERVICE_NAME);
while (en.hasMoreElements()) {
String name = (String) en.nextElement();
Registry registry = (Registry) registries.get(name);
if (registry == null) {
String registryClass = null;
try {
registry = localeService.get(name);
} catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("DatabaseRegistryService: 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 (logger.isDebugEnabled()) {
logger
.debug("DatabaseRegistryService: early init()....end!, this.getInit()= "
+ getInit());
}
}
/**
* @return a Map of all fragments keyed by file names
*/
public Map getFragmentMap() {
return (Map) fragments.clone();
}
/** Late init method from Turbine Service model */
public void init() throws InitializationException {
if (logger.isDebugEnabled()) {
logger.debug("DatabaseRegistryService: Late init called");
}
while (!getInit()) {
// Not yet...
try {
Thread.sleep(500);
if ((verbose > 2) && logger.isDebugEnabled()) {
logger
.debug("DatabaseRegistryService: Waiting for init of Registry...");
}
} catch (InterruptedException ie) {
logger.error(ie);
}
}
if (logger.isDebugEnabled()) {
logger.debug("DatabaseRegistryService: We are done");
}
}
/**
* This is the shutdown method called by the Turbine <code>Service</code>
* framework
*/
public void shutdown() {
this.watcher.setDone();
Iterator i = fragments.keySet().iterator();
while (i.hasNext()) {
saveFragment((String) i.next());
}
}
/**
* 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 (logger.isDebugEnabled()) {
logger.debug("DatabaseRegistryService: Updating the " + regName
+ " registry");
}
int count = 0;
int counDeleted = 0;
LocalRegistry registry = (LocalRegistry) get(regName);
if (registry == null) {
logger.error("DatabaseRegistryService: Null " + name
+ " registry in refresh");
return;
}
Vector toDelete = new Vector();
Iterator i = registry.listEntryNames();
while (i.hasNext()) {
toDelete.add(i.next());
}
// 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) && logger.isDebugEnabled()) {
logger
.debug("DatabaseRegistryService: 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) && logger.isDebugEnabled()) {
logger.debug("DatabaseRegistryService: No changes to entry "
+ entry.getName());
}
} else {
if ((verbose > 1) && logger.isDebugEnabled()) {
logger.debug("DatabaseRegistryService: 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) && logger.isDebugEnabled()) {
logger.debug("DatabaseRegistryService: Adding entry "
+ entry.getName() + " of class " + entry.getClass()
+ " to registry " + name);
}
}
} catch (RegistryException e) {
logger.error(
"DatabaseRegistryService: 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) && logger.isDebugEnabled()) {
logger.debug("DatabaseRegistryService: removing entry " + entryName);
}
// TODO may be I will do it later
// it should delete only portlets which is coming from database
// registry.removeLocalEntry(entryName);
}
if ((verbose > 1) && logger.isDebugEnabled()) {
logger.debug("DatabaseRegistryService: Merged " + count
+ " entries and deleted " + toDelete.size() + " in " + name);
}
}
// 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());
}
}
}
/**
* Load and unmarshal a RegistryFragment from the file
*
* @param file
* the absolute file path storing this fragment
*/
public void loadFragment(String file) {
try {
RegistryFragment fragment = createFragment(file);
// mark this fragment as changed
fragment.setChanged(true);
// if we get here, we successfully loaded the new fragment
updateFragment(file, fragment);
} catch (Throwable t) {
logger.error("DatabaseRegistryService: Could not unmarshal: " + file, t);
}
}
/**
* Read and unmarshal a fragment in memory
*
* @param name
* the name of 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 {
} catch (Throwable t) {
logger.error("DatabaseRegistryService: Could not create fragment: "
+ file, t);
} finally {
try {
reader.close();
} catch (Exception e) {
logger.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) {
/**
* TODO I will implement this should go to database
*/
}
/**
* 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);
}
}
}
/**
* 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 enu = fragment.keys();
while (enu.hasMoreElements()) {
String strReg = (String) enu.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);
}
}
}
}
// class specific implementation
private static List getData(String name) {
List list = null;
try {
DBRegistry BaseClass = (DBRegistry) baseClass.get(name);
if (BaseClass != null) {
list = BaseClass.getXREGDataFromDb();
} else {
logger.warn("DatabaseRegistryService: Base class for service " + name
+ " not found");
}
} catch (Exception ex) {
logger.warn("DatabaseRegistryService: Base class for service " + name
+ " not found");
}
return list;
}
private RegistryFragment createFragment(String regName) {
RegistryFragment fragment = (RegistryFragment) fragments.get(regName);
// Fragment can be (and sometimes is, but should not be) null
if (fragment == null) {
fragment = new RegistryFragment();
fragment.put(regName, new Vector());
} else {
Vector vectRegistry = (Vector) fragment.get(regName);
if (vectRegistry == null) {
fragment.put(regName, new Vector());
}
}
List entries = getData(regName);
if (entries != null) {
for (int i = 0; i < entries.size(); i++) {
fragment.setEntry(regName, (RegistryEntry) entries.get(i));
// mark this fragment so that it's persisted next time
// the registry watcher is running
fragment.setDirty(true);
}
} else {
logger.warn("DatabaseRegistryService:no data fouund for service " + name);
}
return fragment;
}
}