// Copyright 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.hivemind.impl;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.ErrorHandler;
import org.apache.hivemind.HiveMind;
import org.apache.hivemind.HiveMindMessages;
import org.apache.hivemind.Location;
import org.apache.hivemind.Registry;
import org.apache.hivemind.ShutdownCoordinator;
import org.apache.hivemind.SymbolSource;
import org.apache.hivemind.SymbolSourceContribution;
import org.apache.hivemind.internal.ConfigurationPoint;
import org.apache.hivemind.internal.RegistryInfrastructure;
import org.apache.hivemind.internal.ServiceModelFactory;
import org.apache.hivemind.internal.ServicePoint;
import org.apache.hivemind.order.Orderer;
import org.apache.hivemind.schema.Translator;
import org.apache.hivemind.service.ThreadEventNotifier;
import org.apache.hivemind.util.ToStringBuilder;
/**
* Implementation of {@link org.apache.hivemind.Registry}.
*
* @author Howard Lewis Ship
*/
public final class RegistryImpl implements Registry, RegistryInfrastructure
{
private static final String SYMBOL_SOURCES = "hivemind.SymbolSources";
private static final Log LOG = LogFactory.getLog(RegistryImpl.class);
/**
* Map of {@link ServicePoint} keyed on fully qualified service id.
*/
private Map _servicePoints = new HashMap();
/**
* Map of List (of {@link ServicePoint}, keyed on service interface.
*/
private Map _servicePointsByInterface = new HashMap();
/**
* Map of {@link ConfigurationPoint} keyed on fully qualified configuration id.
*/
private Map _configurationPoints = new HashMap();
private SymbolSource[] _variableSources;
private ErrorHandler _errorHandler;
private Locale _locale;
private ShutdownCoordinator _shutdownCoordinator;
/**
* Map of {@link ServiceModelFactory}, keyed on service model name,
* loaded from <code>hivemind.ServiceModels</code> configuration point.
*/
private Map _serviceModelFactories;
private boolean _started = false;
private boolean _shutdown = false;
private ThreadEventNotifier _threadEventNotifier;
private TranslatorManager _translatorManager;
public RegistryImpl(ErrorHandler errorHandler, Locale locale)
{
_errorHandler = errorHandler;
_locale = locale;
_translatorManager = new TranslatorManager(this, errorHandler);
}
public Locale getLocale()
{
return _locale;
}
public void addServicePoint(ServicePoint point)
{
checkStarted();
_servicePoints.put(point.getExtensionPointId(), point);
addServicePointByInterface(point);
}
private void addServicePointByInterface(ServicePoint point)
{
Class key = point.getServiceInterface();
List l = (List) _servicePointsByInterface.get(key);
if (l == null)
{
l = new LinkedList();
_servicePointsByInterface.put(key, l);
}
l.add(point);
}
public void addConfigurationPoint(ConfigurationPoint point)
{
checkStarted();
_configurationPoints.put(point.getExtensionPointId(), point);
}
public ServicePoint getServicePoint(String serviceId)
{
checkShutdown();
ServicePoint result = (ServicePoint) _servicePoints.get(serviceId);
if (result == null)
throw new ApplicationRuntimeException(ImplMessages.noSuchServicePoint(serviceId));
return result;
}
public Object getService(String serviceId, Class serviceInterface)
{
ServicePoint point = getServicePoint(serviceId);
return point.getService(serviceInterface);
}
public Object getService(Class serviceInterface)
{
List servicePoints = (List) _servicePointsByInterface.get(serviceInterface);
if (servicePoints == null)
throw new ApplicationRuntimeException(
ImplMessages.noServicePointForInterface(serviceInterface));
if (servicePoints.size() > 1)
throw new ApplicationRuntimeException(
ImplMessages.multipleServicePointsForInterface(serviceInterface, servicePoints));
ServicePoint sp = (ServicePoint) servicePoints.get(0);
return sp.getService(serviceInterface);
}
public ConfigurationPoint getConfigurationPoint(String configurationId)
{
checkShutdown();
ConfigurationPoint result = (ConfigurationPoint) _configurationPoints.get(configurationId);
if (result == null)
throw new ApplicationRuntimeException(
ImplMessages.noSuchConfiguration(configurationId));
return result;
}
public List getConfiguration(String configurationId)
{
ConfigurationPoint point = getConfigurationPoint(configurationId);
return point.getElements();
}
public String toString()
{
ToStringBuilder builder = new ToStringBuilder(this);
builder.append("locale", _locale);
return builder.toString();
}
private static final int STATE_START = 0;
private static final int STATE_DOLLAR = 1;
private static final int STATE_COLLECT_SYMBOL_NAME = 2;
/**
*
* Note: a little cut-n-paste from {@link org.apache.tapestry.script.AbstractTokenRule}.
*/
public String expandSymbols(String text, Location location)
{
StringBuffer result = new StringBuffer(text.length());
char[] buffer = text.toCharArray();
int state = STATE_START;
int blockStart = 0;
int blockLength = 0;
int symbolStart = -1;
int symbolLength = 0;
int i = 0;
int braceDepth = 0;
boolean anySymbols = false;
while (i < buffer.length)
{
char ch = buffer[i];
switch (state)
{
case STATE_START :
if (ch == '$')
{
state = STATE_DOLLAR;
i++;
continue;
}
blockLength++;
i++;
continue;
case STATE_DOLLAR :
if (ch == '{')
{
state = STATE_COLLECT_SYMBOL_NAME;
i++;
symbolStart = i;
symbolLength = 0;
braceDepth = 1;
continue;
}
// The '$' was just what it was, not the start of a ${} expression
// block, so include it as part of the static text block.
blockLength++;
state = STATE_START;
continue;
case STATE_COLLECT_SYMBOL_NAME :
if (ch != '}')
{
if (ch == '{')
braceDepth++;
i++;
symbolLength++;
continue;
}
braceDepth--;
if (braceDepth > 0)
{
i++;
symbolLength++;
continue;
}
// Hit the closing brace of a symbol.
// Degenerate case: the string "${}".
if (symbolLength == 0)
blockLength += 3;
// Append anything up to the start of the sequence (this is static
// text between symbol references).
if (blockLength > 0)
result.append(buffer, blockStart, blockLength);
if (symbolLength > 0)
{
String variableName =
text.substring(symbolStart, symbolStart + symbolLength);
result.append(expandSymbol(variableName, location));
anySymbols = true;
}
i++;
blockStart = i;
blockLength = 0;
// And drop into state start
state = STATE_START;
continue;
}
}
// If get this far without seeing any variables, then just pass
// the input back.
if (!anySymbols)
return text;
// OK, to handle the end. Couple of degenerate cases where
// a ${...} was incomplete, so we adust the block length.
if (state == STATE_DOLLAR)
blockLength++;
if (state == STATE_COLLECT_SYMBOL_NAME)
blockLength += symbolLength + 2;
if (blockLength > 0)
result.append(buffer, blockStart, blockLength);
return result.toString();
}
private String expandSymbol(String name, Location location)
{
String value = valueForSymbol(name);
if (value != null)
return value;
_errorHandler.error(LOG, ImplMessages.noSuchSymbol(name), location, null);
return "${" + name + "}";
}
public String valueForSymbol(String name)
{
checkShutdown();
SymbolSource[] sources = getSymbolSources();
for (int i = 0; i < sources.length; i++)
{
String value = sources[i].valueForSymbol(name);
if (value != null)
return value;
}
return null;
}
private synchronized SymbolSource[] getSymbolSources()
{
if (_variableSources != null)
return _variableSources;
List contributions = getConfiguration(SYMBOL_SOURCES);
Orderer o =
new Orderer(
LogFactory.getLog(SYMBOL_SOURCES),
_errorHandler,
ImplMessages.symbolSourceContribution());
Iterator i = contributions.iterator();
while (i.hasNext())
{
SymbolSourceContribution c = (SymbolSourceContribution) i.next();
o.add(c, c.getName(), c.getPrecedingNames(), c.getFollowingNames());
}
List sources = o.getOrderedObjects();
int count = sources.size();
_variableSources = new SymbolSource[count];
for (int j = 0; j < count; j++)
{
SymbolSourceContribution c = (SymbolSourceContribution) sources.get(j);
_variableSources[j] = c.getSource();
}
return _variableSources;
}
public void setShutdownCoordinator(ShutdownCoordinator coordinator)
{
_shutdownCoordinator = coordinator;
}
/**
* Invokes {@link ShutdownCoordinator#shutdown()}, then releases
* the coordinator, modules and variable sources.
*/
public void shutdown()
{
checkShutdown();
// Allow service implementations and such to shutdown.
ShutdownCoordinator coordinatorService =
(ShutdownCoordinator) getService("hivemind.ShutdownCoordinator",
ShutdownCoordinator.class);
coordinatorService.shutdown();
_shutdown = true;
// Shutdown infrastructure items, such as proxies.
_shutdownCoordinator.shutdown();
_servicePoints = null;
_servicePointsByInterface = null;
_configurationPoints = null;
_shutdownCoordinator = null;
_variableSources = null;
_serviceModelFactories = null;
_threadEventNotifier = null;
}
private void checkShutdown()
{
if (_shutdown)
throw new ApplicationRuntimeException(HiveMindMessages.registryShutdown());
}
private void checkStarted()
{
if (_started)
throw new IllegalStateException(ImplMessages.registryAlreadyStarted());
}
/**
* Starts up the Registry after all service and configuration points have been defined.
* This locks down the Registry so that no further extension points may be added.
* This method may only be invoked once.
*
* <p>
* In addition, the service <code>hivemind.Startup</code> is obtained and
* <code>run()</code> is invoked on it. This allows additional startup, provided
* in the <code>hivemind.Startup</code> configuration point, to be executed.
*/
public void startup()
{
checkStarted();
_started = true;
Runnable startup = (Runnable) getService("hivemind.Startup", Runnable.class);
startup.run();
}
public synchronized ServiceModelFactory getServiceModelFactory(String name)
{
if (_serviceModelFactories == null)
readServiceModelFactories();
ServiceModelFactory result = (ServiceModelFactory) _serviceModelFactories.get(name);
if (result == null)
throw new ApplicationRuntimeException(ImplMessages.unknownServiceModel(name));
return result;
}
private void readServiceModelFactories()
{
List l = getConfiguration("hivemind.ServiceModels");
_serviceModelFactories = new HashMap();
Iterator i = l.iterator();
while (i.hasNext())
{
ServiceModelContribution smc = (ServiceModelContribution) i.next();
String name = smc.getName();
ServiceModelFactory old = (ServiceModelFactory) _serviceModelFactories.get(name);
if (old != null)
{
_errorHandler.error(
LOG,
ImplMessages.dupeServiceModelName(name, HiveMind.getLocation(old)),
HiveMind.getLocation(smc),
null);
continue;
}
_serviceModelFactories.put(name, smc.getFactory());
}
}
public synchronized void cleanupThread()
{
if (_threadEventNotifier == null)
_threadEventNotifier =
(ThreadEventNotifier) getService("hivemind.ThreadEventNotifier",
ThreadEventNotifier.class);
_threadEventNotifier.fireThreadCleanup();
}
public void setLocale(Locale locale)
{
_locale = locale;
}
public ErrorHandler getErrorHander()
{
return _errorHandler;
}
public Translator getTranslator(String translator)
{
return _translatorManager.getTranslator(translator);
}
}