org.jgroups.stack.Configurator Maven / Gradle / Ivy
package org.jgroups.stack;
import org.jgroups.Event;
import org.jgroups.Global;
import org.jgroups.PhysicalAddress;
import org.jgroups.annotations.LocalAddress;
import org.jgroups.annotations.Property;
import org.jgroups.annotations.RecommendedForUpgrade;
import org.jgroups.conf.PropertyHelper;
import org.jgroups.conf.ProtocolConfiguration;
import org.jgroups.conf.XmlNode;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.TP;
import org.jgroups.util.StackType;
import org.jgroups.util.Util;
import java.lang.reflect.*;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.*;
import java.util.stream.Collectors;
/**
* The task if this class is to setup and configure the protocol stack. A string describing
* the desired setup, which is both the layering and the configuration of each layer, is
* given to the configurator which creates and configures the protocol stack and returns
* a reference to the top layer (Protocol).
* Future functionality will include the capability to dynamically modify the layering
* of the protocol stack and the properties of each layer.
* @author Bela Ban
* @author Richard Achmatowicz
*/
public class Configurator {
protected static final Log log=LogFactory.getLog(Configurator.class);
protected static boolean skip_setting_default_values=false;
protected final ProtocolStack stack;
public Configurator() {
stack=null;
}
public Configurator(ProtocolStack protocolStack) {
stack=protocolStack;
}
public static boolean skipSettingDefaultValues() {return skip_setting_default_values;}
public static void skipSettingDefaultValues(boolean f) {skip_setting_default_values=f;}
public Protocol setupProtocolStack(List config) throws Exception {
return setupProtocolStack(config, stack);
}
/**
* Sets up the protocol stack. Each {@link ProtocolConfiguration} has the protocol name and a map of attribute
* names and values (strings). Reflection is used to find the right fields (or setters) based on attribute names,
* and set them (by converting the attribute value to the proper object).
*/
public static Protocol setupProtocolStack(List protocol_configs, ProtocolStack st) throws Exception {
List protocols=createProtocolsAndInitializeAttrs(protocol_configs, st);
// Fixes NPE with concurrent channel creation when using a shared stack (https://issues.redhat.com/browse/JGRP-1488)
Protocol top_protocol=protocols.get(protocols.size() - 1);
top_protocol.setUpProtocol(st);
return connectProtocols(protocols);
}
public static Protocol createProtocol(String prot_spec, ProtocolStack stack) throws Exception {
return createProtocol(prot_spec, stack, true);
}
/**
* Creates a new protocol given the protocol specification. Initializes the properties and starts the
* up and down handler threads.
* @param prot_spec The specification of the protocol. Same convention as for specifying a protocol stack.
* An exception will be thrown if the class cannot be created. Example:
* "VERIFY_SUSPECT(timeout=1500)"
Note that no colons (:) have to be
* specified
* @param stack The protocol stack
* @return Protocol The newly created protocol
* @throws Exception Will be thrown when the new protocol cannot be created
*/
public static Protocol createProtocol(String prot_spec, ProtocolStack stack, boolean init_attrs) throws Exception {
if(prot_spec == null) throw new Exception("Configurator.createProtocol(): prot_spec is null");
// parse the configuration for this protocol
ProtocolConfiguration config=new ProtocolConfiguration(prot_spec);
// create an instance of the protocol class and configure it
Protocol prot=createLayer(stack, config);
if(init_attrs)
initializeAttrs(prot, config, Util.getIpStackType());
prot.init();
return prot;
}
public static List createProtocolsAndInitializeAttrs(List cfgs,
ProtocolStack st) throws Exception {
List protocols=createProtocols(cfgs, st);
if(protocols == null)
return null;
// Determine how to resolve addresses that are set (e.g.) via symbolic names, by the type of bind_addr in the
// transport. The logic below is described in https://issues.redhat.com/browse/JGRP-2343
StackType ip_version=Util.getIpStackType();
Protocol transport=protocols.get(0);
if(transport instanceof TP) {
ProtocolConfiguration cfg=cfgs.get(0);
Field bind_addr_field=Util.getField(transport.getClass(), "bind_addr");
resolveAndAssignField(transport, bind_addr_field, cfg.getProperties(), ip_version);
InetAddress resolved_addr=(InetAddress)Util.getField(bind_addr_field, transport);
if(resolved_addr != null)
ip_version=resolved_addr instanceof Inet6Address? StackType.IPv6 : StackType.IPv4;
else if(ip_version == StackType.Dual)
ip_version=StackType.IPv4; // prefer IPv4 addresses
}
for(int i=0; i < cfgs.size(); i++) {
ProtocolConfiguration config=cfgs.get(i);
Protocol prot=protocols.get(i);
initializeAttrs(prot, config, ip_version);
}
setDefaultAddressValues(protocols, ip_version);
ensureValidBindAddresses(protocols);
return protocols;
}
/**
* Creates a protocol stack by iterating through the protocol list and connecting
* adjacent layers. The list starts with the topmost layer and has the bottommost
* layer at the tail.
* @param protocol_list List of Protocol elements (from top to bottom)
* @return Protocol stack
*/
public static Protocol connectProtocols(List protocol_list) throws Exception {
Protocol current_layer=null, next_layer=null;
for(int i=0; i < protocol_list.size(); i++) {
current_layer=protocol_list.get(i);
if(i + 1 >= protocol_list.size())
break;
next_layer=protocol_list.get(i + 1);
next_layer.setDownProtocol(current_layer);
current_layer.setUpProtocol(next_layer);
}
// basic protocol sanity check
sanityCheck(protocol_list);
return current_layer;
}
/**
* Takes a list of configurations, creates a protocol for each and returns all protocols in a list.
* @param protocol_configs A list of ProtocolConfigurations
* @param stack The protocol stack
* @return List of Protocols
*/
public static List createProtocols(List protocol_configs, ProtocolStack stack) throws Exception {
List retval=new LinkedList<>();
for(int i=0; i < protocol_configs.size(); i++) {
ProtocolConfiguration protocol_config=protocol_configs.get(i);
Protocol layer=createLayer(stack, protocol_config);
if(layer == null)
return null;
retval.add(layer);
}
return retval;
}
protected static Protocol createLayer(ProtocolStack stack, ProtocolConfiguration config) throws Exception {
String protocol_name=config.getProtocolName();
if(protocol_name == null)
return null;
Class extends Protocol> clazz=config.loadProtocolClass(stack != null? stack.getClass() : null);
if(clazz.getAnnotation(Deprecated.class) != null)
log.warn("%s has been deprecated; please upgrade to a newer version of the protocol", clazz.getSimpleName());
if(clazz.getAnnotation(RecommendedForUpgrade.class) != null)
log.warn("there are more recent versions of %s present; " +
"please upgrade to a newer version of the protocol", clazz.getSimpleName());
try {
Protocol retval=clazz.getDeclaredConstructor().newInstance();
if(stack != null)
retval.setProtocolStack(stack);
return retval;
}
catch(InstantiationException inst_ex) {
throw new InstantiationException(String.format(Util.getMessage("ProtocolCreateError"),
protocol_name, inst_ex.getLocalizedMessage()));
}
}
/** Sets the attributes in a given protocol from properties */
public static void initializeAttrs(Protocol prot, ProtocolConfiguration config, StackType ip_version) throws Exception {
String protocol_name=config.getProtocolName();
if(protocol_name == null)
return;
Map properties=new HashMap<>(config.getProperties());
initializeAttrs(prot, properties, ip_version);
// don't yet initialize attrs of components, but later (after init() has been called); therefore remove all
// properties belonging to components
Util.forAllComponentTypes(prot.getClass(), (obj, prefix) -> {
String key=prefix + ".";
properties.keySet().removeIf(s -> s.startsWith(key));
});
if(!properties.isEmpty())
throw new IllegalArgumentException(String.format(Util.getMessage("ConfigurationError"), protocol_name, properties));
// if we have protocol-specific XML configuration, pass it to the protocol
List subtrees=config.getSubtrees();
if(subtrees != null) {
for(XmlNode node : subtrees)
prot.parse(node);
}
}
public static void initializeAttrs(Object obj, Map properties, StackType ip_version) throws Exception {
// before processing field and method based props, take dependencies specified by @Property.dependsUpon into account
AccessibleObject[] dependencyOrderedFieldsAndMethods=computePropertyDependencies(obj, properties);
for(AccessibleObject ordered : dependencyOrderedFieldsAndMethods) {
if(ordered instanceof Field)
resolveAndAssignField(obj, (Field)ordered, properties, ip_version);
else if(ordered instanceof Method)
resolveAndInvokePropertyMethod(obj, (Method)ordered, properties, ip_version);
}
}
/**
* Throws an exception if sanity check fails. Possible sanity check is uniqueness of all protocol names
*/
public static void sanityCheck(List protocols) throws Exception {
Set ids=new HashSet<>();
for(Protocol protocol : protocols) {
short id=protocol.getId();
String name=protocol.getName();
if(id > 0 && !ids.add(id))
throw new Exception("Protocol ID " + id + " (name=" + name + ") is duplicate; IDs have to be unique");
}
// For each protocol, get its required up and down services and check (if available) if they're satisfied
for(Protocol protocol : protocols) {
List required_down_services=protocol.requiredDownServices();
List required_up_services=protocol.requiredUpServices();
if(required_down_services != null && !required_down_services.isEmpty()) {
// the protocols below 'protocol' have to provide the services listed in required_down_services
List tmp=new ArrayList<>(required_down_services);
removeProvidedUpServices(protocol, tmp);
if(!tmp.isEmpty())
throw new Exception("events " + printEvents(tmp) + " are required by " + protocol.getName() +
", but not provided by any of the protocols below it");
}
if(required_up_services != null && !required_up_services.isEmpty()) {
// the protocols above 'protocol' have to provide the services listed in required_up_services
List tmp=new ArrayList<>(required_up_services);
removeProvidedDownServices(protocol, tmp);
if(!tmp.isEmpty())
throw new Exception("events " + printEvents(tmp) + " are required by " + protocol.getName() +
", but not provided by any of the protocols above it");
}
}
}
protected static String printEvents(List events) {
return events.stream().map(Event::type2String).collect(Collectors.joining(" ", "[", "]"));
}
/**
* Removes all events provided by the protocol below protocol from events
* @param protocol
* @param events
*/
protected static void removeProvidedUpServices(Protocol protocol, List events) {
if(protocol == null || events == null)
return;
for(Protocol prot=protocol.getDownProtocol(); prot != null && !events.isEmpty(); prot=prot.getDownProtocol()) {
List provided_up_services=prot.providedUpServices();
if(provided_up_services != null && !provided_up_services.isEmpty())
events.removeAll(provided_up_services);
}
}
/**
* Removes all events provided by the protocol above protocol from events
* @param protocol
* @param events
*/
protected static void removeProvidedDownServices(Protocol protocol, List events) {
if(protocol == null || events == null)
return;
for(Protocol prot=protocol.getUpProtocol(); prot != null && !events.isEmpty(); prot=prot.getUpProtocol()) {
List provided_down_services=prot.providedDownServices();
if(provided_down_services != null && !provided_down_services.isEmpty())
events.removeAll(provided_down_services);
}
}
/** Returns all inet addresses found */
public static Collection getAddresses(Map> map) throws Exception {
return map.values().stream().flatMap(m -> m.values().stream())
.flatMap(i -> i.getInetAddresses().stream()).filter(Objects::nonNull).collect(Collectors.toSet());
}
/*
* Discovers all fields or methods within the protocol stack which set InetAddress, IpAddress, InetSocketAddress
* (and lists of such) for which the user has specified a default value. Stores the resulting set of fields and
* methods in a map of the form Protocol -> Property -> InetAddressInfo
* where InetAddressInfo instances encapsulate the InetAddress related information of the fields and methods.
*/
public static Map> createInetAddressMap(List protocol_configs,
List protocols) throws Exception {
Map> inetAddressMap=new HashMap<>();
for(int i=0; i < protocol_configs.size(); i++) {
ProtocolConfiguration protocol_config=protocol_configs.get(i);
Protocol protocol=protocols.get(i);
String protocolName=protocol.getName();
// regenerate the properties which were destroyed during basic property processing
Map properties=new HashMap<>(protocol_config.getProperties());
// check which InetAddress-related properties are non-null, and create an InetAddressInfo structure for them
Method[] methods=Util.getAllDeclaredMethodsWithAnnotations(protocol.getClass(), Property.class);
for(int j=0; j < methods.length; j++) {
if(isSetPropertyMethod(methods[j], protocol.getClass())) {
String propertyName=PropertyHelper.getPropertyName(methods[j]);
String propertyValue=properties.get(propertyName);
// if there is a systemProperty attribute defined in the annotation, set the property value from the system property
String tmp=grabSystemProp(methods[j].getAnnotation(Property.class));
if(tmp != null)
propertyValue=tmp;
if(propertyValue != null && InetAddressInfo.isInetAddressRelated(methods[j])) {
Object converted=null;
try {
converted=PropertyHelper.getConvertedValue(protocol, methods[j], properties, propertyValue,
false, Util.getIpStackType());
}
catch(Exception e) {
throw new Exception("string could not be converted for method " + propertyName + " in "
+ protocolName + " with default value " + propertyValue + ".Exception is " + e, e);
}
InetAddressInfo inetinfo=new InetAddressInfo(protocol, methods[j], properties, propertyValue, converted);
Map m=inetAddressMap.computeIfAbsent(protocolName, k -> new HashMap<>());
m.put(propertyName, inetinfo);
}
}
}
// traverse class hierarchy and find all annotated fields and add them to the list if annotated
Field[] fields=Util.getAllDeclaredFieldsWithAnnotations(protocol.getClass(), Property.class);
for(int j=0; j < fields.length; j++) {
String propertyName=PropertyHelper.getPropertyName(fields[j], properties);
String propertyValue=properties.get(propertyName);
// if there is a systemProperty attribute defined in the annotation, set the property value from the system property
String tmp=grabSystemProp(fields[j].getAnnotation(Property.class));
if(tmp != null)
propertyValue=tmp;
if((propertyValue != null || !PropertyHelper.usesDefaultConverter(fields[j]))
&& InetAddressInfo.isInetAddressRelated(fields[j])) {
Object converted=null;
try {
converted=PropertyHelper.getConvertedValue(protocol, fields[j], properties, propertyValue,
false, Util.getIpStackType());
}
catch(Exception e) {
throw new Exception("string could not be converted for method " + propertyName + " in "
+ protocolName + " with default value " + propertyValue + ".Exception is " + e, e);
}
InetAddressInfo inetinfo=new InetAddressInfo(protocol, fields[j], properties, propertyValue, converted);
Map m=inetAddressMap.computeIfAbsent(protocolName, k -> new HashMap<>());
m.put(propertyName, inetinfo);
}
}
}
return inetAddressMap;
}
public static List getInetAddresses(List protocols) throws Exception {
List retval=new LinkedList<>();
// collect InetAddressInfo
for(Protocol protocol : protocols) {
//traverse class hierarchy and find all annotated fields and add them to the list if annotated
for(Class> clazz=protocol.getClass(); clazz != null; clazz=clazz.getSuperclass()) {
Field[] fields=clazz.getDeclaredFields();
for(int j=0; j < fields.length; j++) {
if(fields[j].isAnnotationPresent(Property.class)) {
if(InetAddressInfo.isInetAddressRelated(fields[j])) {
Object value=getValueFromObject(protocol, fields[j]);
if(value instanceof InetAddress)
retval.add((InetAddress)value);
else if(value instanceof IpAddress)
retval.add(((IpAddress)value).getIpAddress());
else if(value instanceof InetSocketAddress)
retval.add(((InetSocketAddress)value).getAddress());
}
}
}
}
}
return retval;
}
/** Returns a map of protocol.attr/InetAddress tuples */
public static Map getInetAddresses2(List protocols) {
Map map=new HashMap<>();
if(protocols != null) {
for(Protocol p: protocols) {
Field[] fields=Util.getAllDeclaredFieldsWithAnnotations(p.getClass(), Property.class);
for(Field f: fields) {
if(InetAddress.class.isAssignableFrom(f.getType())) {
try {
map.put(p.getName() + "." + f.getName(), getValueFromObject(p, f));
}
catch(IllegalAccessException e) {
map.put(p.getName() + "." + f.getName(), null);
}
}
}
}
}
return map;
}
public static boolean isInetAddressOrCompatibleType(Class> c) {
return InetAddress.class.isAssignableFrom(c)
|| SocketAddress.class.isAssignableFrom(c)
|| PhysicalAddress.class.isAssignableFrom(c);
}
/**
* Processes fields and methods which are inet address related, and assigns them default values defined by
* ({@link Property#defaultValueIPv4()} and {@link Property#defaultValueIPv6()}).
* This method does the following:
* - find all properties which have no value assigned
* - generate a value for the field using the property converter for that property and assign it to the field
*/
public static void setDefaultAddressValues(List protocols, StackType ip_version) throws Exception {
if(skip_setting_default_values) {
log.trace("skipped setting default address values in protocols as skip_setting_default_values=%b",
skip_setting_default_values);
return;
}
for(Protocol prot: protocols)
setDefaultAddressValues(prot, ip_version);
}
public static void setDefaultAddressValues(Object obj, StackType ip_version) throws Exception {
InetAddress default_ip_address=Util.getNonLoopbackAddress(ip_version);
if(default_ip_address == null) {
log.warn(Util.getMessage("OnlyLoopbackFound"), ip_version);
default_ip_address=Util.getLoopback(ip_version);
}
// Process the attributes which are defined via methods first
setDefaultAddressValuesMethods(obj, ip_version, default_ip_address);
// Next process the fields
setDefaultAddressValuesFields(obj, ip_version, default_ip_address);
}
protected static void setDefaultAddressValuesMethods(Object obj, StackType ip_version,
InetAddress default_ip_address) throws Exception {
Map properties=new HashMap<>(); // dummy properties
Method[] methods=Util.getAllDeclaredMethodsWithAnnotations(obj.getClass(), Property.class);
for(Method method: methods) {
if(isSetPropertyMethod(method, obj.getClass())) {
String propertyName=PropertyHelper.getPropertyName(method);
Object existing_value=getValueFromObject(obj, propertyName);
if(existing_value != null || !InetAddressInfo.isInetAddressRelated(method))
continue;
Property annotation=method.getAnnotation(Property.class);
String defaultValue=ip_version == StackType.IPv4? annotation.defaultValueIPv4() : annotation.defaultValueIPv6();
if(defaultValue != null && !defaultValue.isEmpty()) {
Object converted=null;
try {
if(defaultValue.equalsIgnoreCase(Global.NON_LOOPBACK_ADDRESS))
converted=default_ip_address;
else
converted=PropertyHelper.getConvertedValue(obj, method, properties,
defaultValue, true, ip_version);
method.invoke(obj, converted);
}
catch(Exception e) {
throw new Exception("default could not be assigned for method " + propertyName + " in "
+ obj + " with default " + defaultValue, e);
}
log.debug("set attribute %s.%s to default value %s", obj, propertyName, converted);
}
}
}
}
protected static void setDefaultAddressValuesFields(Object obj, StackType ip_version,
InetAddress default_ip_address) throws Exception {
Map properties=new HashMap<>(); // dummy properties
Field[] fields=Util.getAllDeclaredFieldsWithAnnotations(obj.getClass(), Property.class);
for(Field field: fields) {
String propertyName=PropertyHelper.getPropertyName(field, properties);
Object existing_value=getValueFromObject(obj, field);
if(existing_value != null || !InetAddressInfo.isInetAddressRelated(field))
continue;
Property annotation=field.getAnnotation(Property.class);
String defaultValue=ip_version == StackType.IPv6? annotation.defaultValueIPv6() : annotation.defaultValueIPv4();
if(defaultValue != null && !defaultValue.isEmpty()) {
// condition for invoking converter
if(defaultValue != null || !PropertyHelper.usesDefaultConverter(field)) {
Object converted=null;
try {
if(defaultValue.equalsIgnoreCase(Global.NON_LOOPBACK_ADDRESS))
converted=default_ip_address;
else
converted=PropertyHelper.getConvertedValue(obj, field, properties,
defaultValue, true, ip_version);
if(converted != null)
Util.setField(field, obj, converted);
}
catch(Exception e) {
throw new Exception("default could not be assigned for field " + propertyName + " in "
+ obj + " with default value " + defaultValue, e);
}
log.debug("set property " + obj + "." + propertyName + " to default value " + converted);
}
}
}
}
/**
* Makes sure that all fields annotated with @LocalAddress is (1) an InetAddress and (2) a valid address on any
* local network interface
*/
public static void ensureValidBindAddresses(List protocols) throws Exception {
for(Protocol protocol : protocols) {
String protocolName=protocol.getName();
//traverse class hierarchy and find all annotated fields and add them to the list if annotated
Field[] fields=Util.getAllDeclaredFieldsWithAnnotations(protocol.getClass(), LocalAddress.class);
for(int i=0; i < fields.length; i++) {
Object val=getValueFromObject(protocol, fields[i]);
if(val == null)
continue;
if(!(val instanceof InetAddress))
throw new Exception("field " + protocolName + "." + fields[i].getName() + " is not an InetAddress");
Util.checkIfValidAddress((InetAddress)val, protocolName);
}
}
}
public static T getValueFromObject(Object obj, Field field) throws IllegalAccessException {
if(obj == null || field == null) return null;
return (T)Util.getField(field, obj);
}
public static T getValueFromObject(Object obj, String field_name) throws IllegalAccessException {
if(obj == null || field_name == null) return null;
Field field=Util.getField(obj.getClass(), field_name);
return field != null? getValueFromObject(obj, field) : null;
}
/**
* This method creates a list of all properties (field or method) in dependency order,
* where dependencies are specified using the dependsUpon specifier of the Property annotation.
* In particular, it does the following:
* (i) creates a master list of properties
* (ii) checks that all dependency references are present
* (iii) creates a copy of the master list in dependency order
*/
static AccessibleObject[] computePropertyDependencies(Object obj, Map properties) {
// List of Fields and Methods of the protocol annotated with @Property
List unorderedFieldsAndMethods=new LinkedList<>();
List orderedFieldsAndMethods=new LinkedList<>();
// Maps property name to property object
Map propertiesInventory=new HashMap<>();
// get the methods for this class and add them to the list if annotated with @Property
Method[] methods=obj.getClass().getMethods();
for(int i=0; i < methods.length; i++) {
if(methods[i].isAnnotationPresent(Property.class) && isSetPropertyMethod(methods[i], obj.getClass())) {
String propertyName=PropertyHelper.getPropertyName(methods[i]);
unorderedFieldsAndMethods.add(methods[i]);
propertiesInventory.put(propertyName, methods[i]);
}
}
//traverse class hierarchy and find all annotated fields and add them to the list if annotated
for(Class> clazz=obj.getClass(); clazz != null; clazz=clazz.getSuperclass()) {
Field[] fields=clazz.getDeclaredFields();
for(int i=0; i < fields.length; i++) {
if(fields[i].isAnnotationPresent(Property.class)) {
String propertyName=PropertyHelper.getPropertyName(fields[i], properties);
unorderedFieldsAndMethods.add(fields[i]);
// may need to change this based on name parameter of Property
propertiesInventory.put(propertyName, fields[i]);
}
}
}
// at this stage, we have all Fields and Methods annotated with @Property
checkDependencyReferencesPresent(unorderedFieldsAndMethods, propertiesInventory);
// order the fields and methods by dependency
orderedFieldsAndMethods=orderFieldsAndMethodsByDependency(unorderedFieldsAndMethods, propertiesInventory);
// convert to array of Objects
AccessibleObject[] result=new AccessibleObject[orderedFieldsAndMethods.size()];
for(int i=0; i < orderedFieldsAndMethods.size(); i++)
result[i]=orderedFieldsAndMethods.get(i);
return result;
}
static List orderFieldsAndMethodsByDependency(List unorderedList,
Map propertiesMap) {
// Stack to detect cycle in depends relation
Deque stack=new ArrayDeque<>();
// the result list
List orderedList=new LinkedList<>();
// add the elements from the unordered list to the ordered list
// any dependencies will be checked and added first, in recursive manner
for(int i=0; i < unorderedList.size(); i++) {
AccessibleObject obj=unorderedList.get(i);
addPropertyToDependencyList(orderedList, propertiesMap, stack, obj);
}
return orderedList;
}
/**
* DFS of dependency graph formed by Property annotations and dependsUpon parameter
* This is used to create a list of Properties in dependency order
*/
static void addPropertyToDependencyList(List orderedList, Map props, Deque stack, AccessibleObject obj) {
if(orderedList.contains(obj))
return;
if(stack.contains(obj))
throw new RuntimeException("Deadlock in @Property dependency processing");
// record the fact that we are processing obj
stack.push(obj);
// process dependencies for this object before adding it to the list
Property annotation=obj.getAnnotation(Property.class);
String dependsClause=annotation.dependsUpon();
StringTokenizer st=new StringTokenizer(dependsClause, ",");
while(st.hasMoreTokens()) {
String token=st.nextToken().trim();
AccessibleObject dep=props.get(token);
// if null, throw exception
addPropertyToDependencyList(orderedList, props, stack, dep);
}
// indicate we're done with processing dependencies
stack.pop();
// we can now add in dependency order
orderedList.add(obj);
}
/*
* Checks that for every dependency referred, there is a matching property
*/
static void checkDependencyReferencesPresent(List objects, Map props) {
for(int i=0; i < objects.size(); i++) { // iterate overall properties marked by @Property
// get the Property annotation
AccessibleObject ao=objects.get(i);
Property annotation=ao.getAnnotation(Property.class);
if(annotation == null)
throw new IllegalArgumentException("@Property annotation is required for checking dependencies;" +
" annotation is missing for Field/Method " + ao);
String dependsClause=annotation.dependsUpon();
if(dependsClause.trim().isEmpty())
continue;
// split dependsUpon specifier into tokens; trim each token; search for token in list
StringTokenizer st=new StringTokenizer(dependsClause, ",");
while(st.hasMoreTokens()) {
String token=st.nextToken().trim();
// check that the string representing a property name is in the list
boolean found=false;
Set keyset=props.keySet();
for(Iterator iter=keyset.iterator(); iter.hasNext(); ) {
if(iter.next().equals(token)) {
found=true;
break;
}
}
if(!found)
throw new IllegalArgumentException("@Property annotation " + annotation.name() +
" has an unresolved dependsUpon property: " + token);
}
}
}
public static void resolveAndInvokePropertyMethods(Object obj, Map props, StackType ip_version) throws Exception {
Method[] methods=obj.getClass().getMethods();
for(Method method : methods) {
resolveAndInvokePropertyMethod(obj, method, props, ip_version);
}
}
public static void resolveAndInvokePropertyMethod(Object obj, Method method, Map props,
StackType ip_version) throws Exception {
String methodName=method.getName();
Property annotation=method.getAnnotation(Property.class);
if(annotation != null && isSetPropertyMethod(method, obj.getClass())) {
String propertyName=PropertyHelper.getPropertyName(method);
String propertyValue=props.get(propertyName);
// if there is a systemProperty attribute defined in the annotation, set the property value from the system property
String tmp=grabSystemProp(method.getAnnotation(Property.class));
if(tmp != null)
propertyValue=tmp;
if(propertyName != null && propertyValue != null) {
String deprecated_msg=annotation.deprecatedMessage();
if(deprecated_msg != null && !deprecated_msg.isEmpty()) {
log.warn(Util.getMessage("Deprecated"), method.getDeclaringClass().getSimpleName() + "." + methodName,
deprecated_msg);
}
}
if(propertyValue != null) {
Object converted=null;
try {
converted=PropertyHelper.getConvertedValue(obj, method, props, propertyValue, true, ip_version);
method.invoke(obj, converted);
}
catch(Exception e) {
String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName();
throw new Exception("Could not assign property " + propertyName + " in "
+ name + ", method is " + methodName + ", converted value is " + converted, e);
}
}
props.remove(propertyName);
}
}
public static void resolveAndAssignFields(Object obj, Map props, StackType ip_version) throws Exception {
//traverse class hierarchy and find all annotated fields
for(Class> clazz=obj.getClass(); clazz != null; clazz=clazz.getSuperclass()) {
Field[] fields=clazz.getDeclaredFields();
for(Field field : fields)
resolveAndAssignField(obj, field, props, ip_version);
}
}
public static void resolveAndAssignField(Object obj, Field field, Map props, StackType ip_version) throws Exception {
Property annotation=field.getAnnotation(Property.class);
if(annotation != null) {
String propertyName=PropertyHelper.getPropertyName(field, props);
String propertyValue=props.get(propertyName);
// if there is a systemProperty attribute defined in the annotation, set the property value from the system property
// only do this if the property value hasn't yet been set
if(propertyValue == null) {
String tmp=grabSystemProp(field.getAnnotation(Property.class));
if(tmp != null)
propertyValue=tmp;
}
if(propertyName != null && propertyValue != null) {
String deprecated_msg=annotation.deprecatedMessage();
if(deprecated_msg != null && !deprecated_msg.isEmpty()) {
log.warn(Util.getMessage("Deprecated"), field.getDeclaringClass().getSimpleName() + "." + field.getName(),
deprecated_msg);
}
}
if(propertyValue != null || !PropertyHelper.usesDefaultConverter(field)) {
Object converted=null;
try {
converted=PropertyHelper.getConvertedValue(obj, field, props, propertyValue, true, ip_version);
if(converted != null)
Util.setField(field, obj, converted);
}
catch(Exception e) {
String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName();
throw new Exception("Property assignment of " + propertyName + " in "
+ name + " with original property value " + propertyValue + " and converted to " + converted
+ " could not be assigned", e);
}
}
props.remove(propertyName);
}
}
public static boolean isSetPropertyMethod(Method method, Class> enclosing_clazz) {
return (method.getReturnType() == java.lang.Void.TYPE || method.getReturnType().isAssignableFrom(enclosing_clazz))
&& method.getParameterTypes().length == 1;
}
private static String grabSystemProp(Property annotation) {
String[] system_property_names=annotation.systemProperty();
String retval=null;
for(String system_property_name : system_property_names) {
if(system_property_name != null && !system_property_name.isEmpty()) {
try {
retval=System.getProperty(system_property_name);
if(retval != null)
return retval;
}
catch(SecurityException ex) {
log.error(Util.getMessage("SyspropFailure"), system_property_name, ex);
}
try {
retval=System.getenv(system_property_name);
if(retval != null)
return retval;
}
catch(SecurityException ex) {
log.error(Util.getMessage("SyspropFailure"), system_property_name, ex);
}
}
}
return retval;
}
/* --------------------------- End of Private Methods ---------------------------------- */
public static class InetAddressInfo {
Protocol protocol;
AccessibleObject fieldOrMethod;
Map properties;
String propertyName;
String stringValue;
Object convertedValue;
boolean isField;
boolean isParameterized; // is the associated type parametrized? (e.g. Collection)
Object baseType; // what is the base type (e.g. Collection)
InetAddressInfo(Protocol protocol, AccessibleObject fieldOrMethod, Map properties, String stringValue,
Object convertedValue) {
// check input values
if(protocol == null) {
throw new IllegalArgumentException("Protocol for Field/Method must be non-null");
}
if(fieldOrMethod instanceof Field) {
isField=true;
}
else if(fieldOrMethod instanceof Method) {
isField=false;
}
else
throw new IllegalArgumentException("AccesibleObject is neither Field nor Method");
if(properties == null) {
throw new IllegalArgumentException("Properties for Field/Method must be non-null");
}
// set the values passed by the user - need to check for null
this.protocol=protocol;
this.fieldOrMethod=fieldOrMethod;
this.properties=properties;
this.stringValue=stringValue;
this.convertedValue=convertedValue;
// set the property name
if(isField())
propertyName=PropertyHelper.getPropertyName((Field)fieldOrMethod, properties);
else
propertyName=PropertyHelper.getPropertyName((Method)fieldOrMethod);
// is variable type parameterized
this.isParameterized=false;
if(isField())
this.isParameterized=hasParameterizedType((Field)fieldOrMethod);
else
this.isParameterized=hasParameterizedType((Method)fieldOrMethod);
// if parameterized, what is the base type?
this.baseType=null;
if(isField() && isParameterized) {
// the Field has a single type
ParameterizedType fpt=(ParameterizedType)((Field)fieldOrMethod).getGenericType();
this.baseType=fpt.getActualTypeArguments()[0];
}
else if(!isField() && isParameterized) {
// the Method has several parameters (and so types)
Type[] types=((Method)fieldOrMethod).getGenericParameterTypes();
ParameterizedType mpt=(ParameterizedType)types[0];
this.baseType=mpt.getActualTypeArguments()[0];
}
}
boolean isField() {
return isField;
}
String getStringValue() {
return stringValue;
}
String getPropertyName() {
return propertyName;
}
Object getConvertedValue() {
return convertedValue;
}
boolean isParameterized() {
return isParameterized;
}
Object getBaseType() {
return baseType;
}
static boolean hasParameterizedType(Field f) {
if(f == null) {
throw new IllegalArgumentException("Field argument is null");
}
Type type=f.getGenericType();
return (type instanceof ParameterizedType);
}
static boolean hasParameterizedType(Method m) throws IllegalArgumentException {
if(m == null) {
throw new IllegalArgumentException("Method argument is null");
}
Type[] types=m.getGenericParameterTypes();
return (types[0] instanceof ParameterizedType);
}
static boolean isInetAddressRelated(Field f) {
if(hasParameterizedType(f)) {
// check for List, List, List
ParameterizedType fieldtype=(ParameterizedType)f.getGenericType();
// check that this parameterized type satisfies our constraints
try {
parameterizedTypeSanityCheck(fieldtype);
}
catch(IllegalArgumentException e) {
// because this Method's parameter fails the sanity check, its probably not an InetAddress related structure
return false;
}
Class> listType=(Class>)fieldtype.getActualTypeArguments()[0];
return isInetAddressOrCompatibleType(listType);
}
else {
// check if the non-parameterized type is InetAddress, InetSocketAddress or IpAddress
Class> fieldtype=f.getType();
return isInetAddressOrCompatibleType(fieldtype);
}
}
/*
* Checks if this method's single parameter represents of of the following:
* an InetAddress, IpAddress or InetSocketAddress or one of
* List, List or List
*/
static boolean isInetAddressRelated(Method m) {
if(hasParameterizedType(m)) {
Type[] types=m.getGenericParameterTypes();
ParameterizedType methodParamType=(ParameterizedType)types[0];
// check that this parameterized type satisfies our constraints
try {
parameterizedTypeSanityCheck(methodParamType);
}
catch(IllegalArgumentException e) {
// because this Method's parameter fails the sanity check, its probably not an InetAddress
return false;
}
Class> listType=(Class>)methodParamType.getActualTypeArguments()[0];
return isInetAddressOrCompatibleType(listType);
}
else {
Class> methodParamType=m.getParameterTypes()[0];
return isInetAddressOrCompatibleType(methodParamType);
}
}
/*
* Check if the parameterized type represents one of:
* List, List, List
*/
static void parameterizedTypeSanityCheck(ParameterizedType pt) throws IllegalArgumentException {
Type rawType=pt.getRawType();
Type[] actualTypes=pt.getActualTypeArguments();
// constraints on use of parameterized types with @Property
if(!(rawType instanceof Class> && Collection.class.isAssignableFrom((Class>)rawType))) {
throw new IllegalArgumentException("Invalid parameterized type definition - parameterized type must be a collection");
}
// check for non-parameterized type in List
if(!(actualTypes[0] instanceof Class>)) {
throw new IllegalArgumentException("Invalid parameterized type - List must not contain a parameterized type");
}
}
/*
* Converts the computedValue to a list of InetAddresses.
* Because computedValues may be null, we need to return
* a zero length list in some cases.
*/
public List getInetAddresses() {
List addresses=new ArrayList<>();
if(getConvertedValue() == null)
return addresses;
// if we take only an InetAddress argument
if(!isParameterized())
addresses.add(getInetAddress(getConvertedValue()));
// if we take a List or similar
else {
List> values=(List>)getConvertedValue();
if(values.isEmpty())
return addresses;
for(int i=0; i < values.size(); i++)
addresses.add(getInetAddress(values.get(i)));
}
return addresses;
}
private static InetAddress getInetAddress(Object obj) throws IllegalArgumentException {
if(obj == null)
throw new IllegalArgumentException("Input argument must represent a non-null IP address");
if(obj instanceof InetAddress)
return (InetAddress)obj;
else if(obj instanceof IpAddress)
return ((IpAddress)obj).getIpAddress();
else if(obj instanceof InetSocketAddress)
return ((InetSocketAddress)obj).getAddress();
else
throw new IllegalArgumentException("Input argument does not represent one of InetAddress. IpAddress not InetSocketAddress");
}
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append("InetAddressInfo(")
.append("protocol=" + protocol.getName())
.append(", propertyName=" + getPropertyName())
.append(", string value=" + getStringValue())
.append(", parameterized=" + isParameterized());
if(isParameterized())
sb.append(", baseType=" + getBaseType());
sb.append(")");
return sb.toString();
}
}
}