/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.config.annotations;
import com.ericsson.ssa.config.ConfigFactory;
import com.ericsson.ssa.config.ConfigRuntimeException;
import com.ericsson.ssa.config.event.ConfigAddEvent;
import com.ericsson.ssa.config.event.ConfigChangeListener;
import com.ericsson.ssa.config.event.ConfigRemoveEvent;
import com.ericsson.ssa.config.event.ConfigUpdateEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.jvnet.glassfish.comms.util.LogUtil;
import java.util.logging.Logger;
/**
*
* @author elnelbo
*/
//TODO Optimization to reduce the number of listeners for a specific node.
//Now for each annotation possibly a listeners is registered.
//This can be done by seperating the Injector from the listeners and have the
// listerner call all associated injectors to do injection.
public class ConfigurationAnnotationIntrospector {
private static final ConfigurationAnnotationIntrospector INSTANCE =
new ConfigurationAnnotationIntrospector();
private Map<Object, List<ConfigInjection>> configurables =
new HashMap<Object, List<ConfigInjection>>();
private Logger log = LogUtil.SIP_LOGGER.getLogger();
private ConfigurationAnnotationIntrospector() {
}
public static ConfigurationAnnotationIntrospector instance() {
return INSTANCE;
}
public void activateConfiguration(Object configurable) {
if (configurable!=null && !configurables.containsKey(configurable)) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Activating Configuration for Object "+
configurable.getClass().getName()+":"+
configurable.hashCode());
}
List<ConfigInjection> configInjectors =
new ArrayList<ConfigInjection>();
configurables.put(configurable, configInjectors);
for (Method method : configurable.getClass().getMethods()) {
Configuration annotation =
method.getAnnotation(Configuration.class);
if (annotation!=null) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Annotated Method found:"+method);
}
String key = getCompatibleKey(annotation, method);
Class propertyType = getCompatibleArgument(method);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Method is compatable");
}
ConfigInjection configInjector = null;
switch (annotation.update()) {
case STARTUP:
configInjector = new StartupConfigInjector(key, configurable,
method, propertyType, annotation);
break;
case DYNAMIC:
configInjector = new DynamicConfigInjector(key, configurable,
method, propertyType, annotation);
break;
}
configInjectors.add(configInjector);
configInjector.setup();
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Injector setup: "+configInjector);
}
}
}
}
}
public void deactivateConfiguration(Object configurable) {
List<ConfigInjection> configInjectors =
configurables.remove(configurable);
if (configInjectors!=null) {
for (ConfigInjection configInjector : configInjectors) {
configInjector.cleanup();
}
}
}
private String getCompatibleKey(Configuration annotation, Method method) {
String key = annotation.key();
//Key is not specified, take the property name from the setter
//Note couldn't find a propery BeanUtil that does the trick easily.
if (key.length()==0) {
key = method.getName();
if (key.startsWith("set") && key.length()>"set".length()) {
key = Character.toUpperCase(key.charAt("set".length())) +
key.substring("set".length()+1);
} else {
if (log.isLoggable(Level.CONFIG)) {
log.log(Level.CONFIG, "sip.common.config_annotations_no_javabean_property_setter",
method.getName());
}
throw new ConfigRuntimeException("sip.common.config_annotations_activation_failed_exception");
}
}
return key;
}
private Class getCompatibleArgument(Method method) {
Class propertyType = null;
Class[] paramTypes = method.getParameterTypes();
if (paramTypes.length == 1) {
try {
if (!paramTypes[0].isPrimitive()) {
paramTypes[0].getConstructor(String.class);
}
propertyType = paramTypes[0];
} catch (NoSuchMethodException ex) {
if (log.isLoggable(Level.CONFIG)) {
log.log(Level.CONFIG, "sip.common.config_annotations_in_compatable_method_signature",
new Object[] { method.getName(),
"arguments type must have have constructure with string argument" });
}
throw new ConfigRuntimeException("sip.common.config_annotations_activation_failed_exception");
}
} else {
if (log.isLoggable(Level.CONFIG)) {
log.log(Level.CONFIG, "sip.common.config_annotations_in_compatable_method_signature",
new Object[] { method.getName(),
"nr of arguments must be 1" });
}
throw new ConfigRuntimeException("sip.common.config_annotations_activation_failed_exception");
}
return propertyType;
}
private interface ConfigInjection {
void setup();
void cleanup();
}
private class StartupConfigInjector implements ConfigInjection {
protected Object configurable;
protected Method method;
protected Class propertyType;
protected Configuration annotation;
protected String key;
private String lastValue;
public StartupConfigInjector(String aKey, Object aConfigurable,
Method aMethod,
Class aPropertyType,
Configuration anAnnotation) {
key = aKey;
configurable = aConfigurable;
method = aMethod;
propertyType = aPropertyType;
annotation = anAnnotation;
lastValue = null;
}
public synchronized void setup() {
String value = getCurrentConfigValue();
inject(value, annotation.usageAtStartup());
lastValue = value;
}
public synchronized void injectChange() {
String value = getCurrentConfigValue();
if (isValueChanged(value)) {
inject(value, annotation.usage());
lastValue = value;
}
}
public synchronized void inject(String value, UsagePolicy usage) {
String resolvedValue = value;
Object arg = null;
if (value==null) {
switch(usage) {
case FAIL:
if (log.isLoggable(Level.SEVERE)) {
log.log(Level.SEVERE, "sip.common.config_annotations_FAIL_usage_on_missing_or_incompatible_value",
new Object[] { method.getName(), key, annotation.node() });
}
throw new ConfigRuntimeException("sip.common.config_annotations_FAIL_usage_exception");
case WARN:
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, "sip.common.config_annotations_WARN_usage_on_missing_or_incompatible_value",
new Object[] { method.getName(), key, annotation.node() });
}
break;
case IGNORE:
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "sip.common.config_annotations_IGNORE_usage_on_missing_or_incompatible_value",
new Object[] { method.getName(), key, annotation.node() });
}
break;
case DEFAULT:
if (log.isLoggable(Level.CONFIG)) {
log.log(Level.CONFIG, "sip.common.config_annotations_DEFAULT_usage_on_missing_or_incompatible_value",
new Object[] { method.getName(), key, annotation.node(), annotation.value() });
}
resolvedValue = annotation.value();
break;
}
}
if (resolvedValue != null)
arg = resolveArg(resolvedValue, usage);
if (arg!=null) {
try {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Injecting arg "+arg+" in Method "+method);
}
method.invoke(configurable, arg);
} catch (IllegalAccessException ex) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, "sip.common.config_annotations_injection_failed_exception_on_invoke",
new Object[] { method.getName(), key, annotation.node() });
log.log(Level.WARNING, ex.getMessage(), ex);
}
} catch (IllegalArgumentException ex) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, "sip.common.config_annotations_injection_failed_exception_on_invoke",
new Object[] { method.getName(), key, annotation.node() });
log.log(Level.WARNING, ex.getMessage(), ex);
}
} catch (InvocationTargetException ex) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, "sip.common.config_annotations_injection_failed_exception_on_invoke",
new Object[] { method.getName(), key, annotation.node() });
log.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
protected String getCurrentConfigValue() {
String value = null;
String node = annotation.node();
node = stripTrailingSlash(node);
if (node!=null && annotation.node().length() > 0) {
value = ConfigFactory.getConfig().get(annotation.node(), key);
} else {
value = ConfigFactory.getConfig().get(key);
}
return value;
}
protected String stripTrailingSlash(String str) {
String stripped = str!=null && str.endsWith("/") ?
str.substring(str.length()-1): str;
return stripped;
}
protected boolean isValueChanged(String aValue) {
/* Irrespective of lastValue, we shouldn't allow setting of null values
* So if value changes from 'x' -> null, do not inject
*/
return aValue!=null ? !aValue.equals(lastValue) : false;
}
protected Object resolveArg(String value, UsagePolicy usage) throws IllegalArgumentException, SecurityException {
Object arg = null;
boolean done = false;
while (!done) {
try {
Class argType = !propertyType.isPrimitive() ?
propertyType :
getWrapperForPrimitiveArg();
if (argType!=Character.class) {
arg = argType.getConstructor(
String.class).newInstance(value);
} else {
//Special case for Character, who doesn't have a
//constructor taking a String as argument.
//We take the first Character of the value.
arg = value.charAt(0);
}
done = true;
} catch (Exception ex) {
switch(usage) {
case FAIL:
done=true;
if (log.isLoggable(Level.SEVERE)) {
log.log(Level.SEVERE, "sip.common.config_annotations_FAIL_usage_on_missing_or_incompatible_value",
new Object[] { method.getName(), key, annotation.node(), value });
log.log(Level.SEVERE, ex.getMessage(), ex);
}
throw new ConfigRuntimeException("sip.common.config_annotations_FAIL_usage_exception");
case WARN:
done=true;
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, "sip.common.config_annotations_WARN_usage_on_missing_or_incompatible_value",
new Object[] { method.getName(), key, annotation.node(), value });
log.log(Level.WARNING, ex.getMessage(), ex);
}
break;
case IGNORE:
done=true;
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "sip.common.config_annotations_IGNORE_usage_on_missing_or_incompatible_value",
new Object[] { method.getName(), key, annotation.node(), value });
log.log(Level.FINEST, ex.getMessage(), ex);
}
break;
case DEFAULT:
if (value!=annotation.value()) /* intentional ref compare */ {
if (log.isLoggable(Level.CONFIG)) {
log.log(Level.CONFIG, "sip.common.config_annotations_DEFAULT_usage_on_missing_or_incompatible_value",
new Object[] { method.getName(), key, annotation.node(), annotation.value(), value });
log.log(Level.CONFIG, ex.getMessage(), ex);
}
value = annotation.value();
} else {
done = true;
}
break;
}
}
}
return arg;
}
private Class getWrapperForPrimitiveArg() {
Class wrapper = null;
if (propertyType==Boolean.TYPE) {
wrapper = Boolean.class;
} else if (propertyType==Byte.TYPE) {
wrapper = Byte.class;
} else if (propertyType==Short.TYPE) {
wrapper = Short.class;
} else if (propertyType==Integer.TYPE) {
wrapper = Integer.class;
} else if (propertyType==Long.TYPE) {
wrapper = Long.class;
} else if (propertyType==Float.TYPE) {
wrapper = Float.class;
} else if (propertyType==Double.TYPE) {
wrapper = Double.class;
} else if (propertyType==Character.TYPE) {
wrapper = Character.class;
}
return wrapper;
}
public synchronized void cleanup() {
configurable = null;
method = null;
propertyType = null;
annotation = null;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer("ConfigInjection: ");
buf.append(getClass().getName());
buf.append(" {key=");
buf.append(key);
buf.append(", node=");
buf.append(annotation.node());
buf.append(", method=");
buf.append(method.getName());
buf.append(", property type=");
buf.append(propertyType.getName());
buf.append("}");
return buf.toString();
}
}
private class DynamicConfigInjector extends StartupConfigInjector
implements ConfigChangeListener {
private DynamicConfigInjector(String aKey, Object aConfigurable,
Method aMethod,
Class aPropertyType,
Configuration anAnnotation) {
super(aKey, aConfigurable, aMethod, aPropertyType, anAnnotation);
}
@Override
public synchronized void setup() {
ConfigFactory.instance().registerConfigChangeListener(
stripTrailingSlash(annotation.node()), this);
super.setup();
}
@Override
public synchronized void cleanup() {
ConfigFactory.instance().deregisterConfigChangeListener(this);
super.cleanup();
}
public void handleConfigEvent(ConfigAddEvent event) {
injectChange();
}
public void handleConfigEvent(ConfigUpdateEvent event) {
injectChange();
}
public void handleConfigEvent(ConfigRemoveEvent event) {
injectChange();
}
}
}