package org.jgroups.stack; import org.jgroups.Event; import org.jgroups.Global; import org.jgroups.annotations.DeprecatedProperty; import org.jgroups.annotations.LocalAddress; import org.jgroups.annotations.Property; import org.jgroups.conf.PropertyHelper; import org.jgroups.conf.ProtocolConfiguration; import org.jgroups.logging.Log; import org.jgroups.logging.LogFactory; import org.jgroups.util.StackType; import org.jgroups.util.Util; import org.w3c.dom.Node; import java.lang.reflect.*; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.*; /** * 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).<p> * 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); private final ProtocolStack stack; public Configurator() { stack=null; } public Configurator(ProtocolStack protocolStack) { stack=protocolStack; } public Protocol setupProtocolStack(List<ProtocolConfiguration> config) throws Exception { return setupProtocolStack(config, stack); } public Protocol setupProtocolStack(ProtocolStack copySource) throws Exception { List<Protocol> protocols=copySource.copyProtocols(stack); Collections.reverse(protocols); return connectProtocols(protocols); } /** * The configuration string has a number of entries, separated by a ':' (colon). * Each entry consists of the name of the protocol, followed by an optional configuration * of that protocol. The configuration is enclosed in parentheses, and contains entries * which are name/value pairs connected with an assignment sign (=) and separated by * a semicolon. * <pre>UDP(in_port=5555;out_port=4445):FRAG(frag_size=1024)</pre><p> * The <em>first</em> entry defines the <em>bottommost</em> layer, the string is parsed * left to right and the protocol stack constructed bottom up. Example: the string * "UDP(in_port=5555):FRAG(frag_size=32000):DEBUG" results is the following stack:<pre> * * ----------------------- * | DEBUG | * |-----------------------| * | FRAG frag_size=32000 | * |-----------------------| * | UDP in_port=32000 | * ----------------------- * </pre> */ public static Protocol setupProtocolStack(List<ProtocolConfiguration> protocol_configs, ProtocolStack st) throws Exception { List<Protocol> protocols=createProtocols(protocol_configs, st); if(protocols == null) return null; // check InetAddress related features of stack Map<String, Map<String,InetAddressInfo>> inetAddressMap = createInetAddressMap(protocol_configs, protocols) ; Collection<InetAddress> addrs=getAddresses(inetAddressMap); StackType ip_version=Util.getIpStackType(); // 0 = n/a, 4 = IPv4, 6 = IPv6 if(!addrs.isEmpty()) { // check that all user-supplied InetAddresses have a consistent version: // 1. If an addr is IPv6 and we have an IPv4 stack --> FAIL // 2. If an address is an IPv4 class D (multicast) address and the stack is IPv6: FAIL // Else pass for(InetAddress addr: addrs) { if(addr instanceof Inet6Address && ip_version == StackType.IPv4) throw new IllegalArgumentException("found IPv6 address " + addr + " in an IPv4 stack"); if(addr instanceof Inet4Address && addr.isMulticastAddress() && ip_version == StackType.IPv6) throw new Exception("found IPv4 multicast address " + addr + " in an IPv6 stack"); } } // process default values setDefaultValues(protocol_configs, protocols, ip_version); ensureValidBindAddresses(protocols); // Fixes NPE with concurrent channel creation when using a shared stack (https://issues.jboss.org/browse/JGRP-1488) Protocol top_protocol=protocols.get(protocols.size() - 1); top_protocol.setUpProtocol(st); return connectProtocols(protocols); } public static void setDefaultValues(List<Protocol> protocols) throws Exception { if(protocols == null) return; // check InetAddress related features of stack Collection<InetAddress> addrs=getInetAddresses(protocols); StackType ip_version=Util.getIpStackType(); // 0 = n/a, 4 = IPv4, 6 = IPv6 if(!addrs.isEmpty()) { // check that all user-supplied InetAddresses have a consistent version: // 1. If an addr is IPv6 and we have an IPv4 stack --> FAIL // 2. If an address is an IPv4 class D (multicast) address and the stack is IPv6: FAIL // Else pass for(InetAddress addr : addrs) { if(addr instanceof Inet6Address && ip_version == StackType.IPv4) throw new IllegalArgumentException("found IPv6 address " + addr + " in an IPv4 stack"); if(addr instanceof Inet4Address && addr.isMulticastAddress() && ip_version == StackType.IPv6) throw new Exception("found IPv4 multicast address " + addr + " in an IPv6 stack"); } } // process default values setDefaultValues(protocols, ip_version); } /** * 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: * <pre>"VERIFY_SUSPECT(timeout=1500)"</pre> Note that no colons (:) have to be * specified * @param stack The protocol stack * @return Protocol The newly created protocol * @exception Exception Will be thrown when the new protocol cannot be created */ public static Protocol createProtocol(String prot_spec, ProtocolStack stack) throws Exception { ProtocolConfiguration config; Protocol prot; if(prot_spec == null) throw new Exception("Configurator.createProtocol(): prot_spec is null"); // parse the configuration for this protocol config=new ProtocolConfiguration(prot_spec); // create an instance of the protocol class and configure it prot=createLayer(stack, config); prot.init(); return prot; } /* ------------------------------- Private Methods ------------------------------------- */ /** * 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> 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 vector of ProtocolConfigurations, iterates through it, creates Protocol for * each ProtocolConfiguration and returns all Protocols in a list. * @param protocol_configs List of ProtocolConfigurations * @param stack The protocol stack * @return List of Protocols */ public static List<Protocol> createProtocols(List<ProtocolConfiguration> protocol_configs, final ProtocolStack stack) throws Exception { List<Protocol> 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(); Map<String, String> properties=new HashMap<>(config.getProperties()); Protocol retval=null; if(protocol_name == null || properties == null) return null; String defaultProtocolName=ProtocolConfiguration.protocol_prefix + '.' + protocol_name; Class<?> clazz=null; try { clazz=Util.loadClass(defaultProtocolName, stack != null? stack.getClass() : null); } catch(ClassNotFoundException e) { } if(clazz == null) { try { clazz=Util.loadClass(protocol_name, config.getClassLoader()); } catch(ClassNotFoundException e) { } if(clazz == null) throw new Exception(String.format(Util.getMessage("ProtocolLoadError"), protocol_name, defaultProtocolName)); } try { retval=(Protocol)clazz.newInstance(); if(stack != null) retval.setProtocolStack(stack); removeDeprecatedProperties(retval, properties); // before processing Field and Method based properties, take dependencies specified // with @Property.dependsUpon into account AccessibleObject[] dependencyOrderedFieldsAndMethods = computePropertyDependencies(retval, properties) ; for(AccessibleObject ordered: dependencyOrderedFieldsAndMethods) { if (ordered instanceof Field) { resolveAndAssignField(retval, (Field)ordered, properties) ; } else if (ordered instanceof Method) { resolveAndInvokePropertyMethod(retval, (Method)ordered, properties) ; } } List<Object> additional_objects=retval.getConfigurableObjects(); if(additional_objects != null && !additional_objects.isEmpty()) { for(Object obj: additional_objects) { resolveAndAssignFields(obj, properties); resolveAndInvokePropertyMethods(obj, properties); } } 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<Node> subtrees=config.getSubtrees(); if(subtrees != null) { for(Node node: subtrees) retval.parse(node); } } catch(InstantiationException inst_ex) { throw new InstantiationException(String.format(Util.getMessage("ProtocolCreateError"), protocol_name, inst_ex.getLocalizedMessage())); } return retval; } /** Throws an exception if sanity check fails. Possible sanity check is uniqueness of all protocol names */ public static void sanityCheck(List<Protocol> protocols) throws Exception { // check for unique IDs Set<Short> ids=new HashSet<>(); for(Protocol protocol: protocols) { short id=protocol.getId(); if(id > 0 && !ids.add(id)) throw new Exception("Protocol ID " + id + " (name=" + protocol.getName() + ") is duplicate; protocol 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<Integer> required_down_services=protocol.requiredDownServices(); List<Integer> 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<Integer> 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<Integer> 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<Integer> events) { StringBuilder sb=new StringBuilder("["); for(int evt: events) sb.append(Event.type2String(evt)).append(" "); sb.append("]"); return sb.toString(); } /** * Removes all events provided by the protocol below protocol from events * @param protocol * @param events */ protected static void removeProvidedUpServices(Protocol protocol, List<Integer> events) { if(protocol == null || events == null) return; for(Protocol prot=protocol.getDownProtocol(); prot != null && !events.isEmpty(); prot=prot.getDownProtocol()) { List<Integer> 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<Integer> events) { if(protocol == null || events == null) return; for(Protocol prot=protocol.getUpProtocol(); prot != null && !events.isEmpty(); prot=prot.getUpProtocol()) { List<Integer> 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<InetAddress> getAddresses(Map<String, Map<String, InetAddressInfo>> inetAddressMap) throws Exception { Set<InetAddress> addrs=new HashSet<>(); for(Map.Entry<String, Map<String, InetAddressInfo>> inetAddressMapEntry : inetAddressMap.entrySet()) { Map<String, InetAddressInfo> protocolInetAddressMap=inetAddressMapEntry.getValue(); for(Map.Entry<String, InetAddressInfo> protocolInetAddressMapEntry : protocolInetAddressMap.entrySet()) { InetAddressInfo inetAddressInfo=protocolInetAddressMapEntry.getValue(); // add InetAddressInfo to sets based on IP version List<InetAddress> addresses=inetAddressInfo.getInetAddresses(); for(InetAddress address : addresses) { if(address == null) throw new RuntimeException("failed converting address info to IP address: " + inetAddressInfo); addrs.add(address); } } } return addrs; } /** * This method takes a set of InetAddresses, represented by an inetAddressmap, and: * - if the resulting set is non-empty, goes through to see if all InetAddress-related * user settings have a consistent IP version: v4 or v6, and throws an exception if not * - if the resulting set is empty, sets the default IP version based on available stacks * and if a dual stack, stack preferences * - sets the IP version to be used in the JGroups session * @return StackType.IPv4 for IPv4, StackType.IPv6 for IPv6, StackType.Unknown if the version cannot be determined */ public static StackType determineIpVersionFromAddresses(Collection<InetAddress> addrs) throws Exception { Set<InetAddress> ipv4_addrs= new HashSet<>() ; Set<InetAddress> ipv6_addrs= new HashSet<>() ; for(InetAddress address: addrs) { if (address instanceof Inet4Address) ipv4_addrs.add(address) ; else ipv6_addrs.add(address) ; } log.trace("all addrs=" + addrs + ", IPv4 addrs=" + ipv4_addrs + ", IPv6 addrs=" + ipv6_addrs); // the user supplied 1 or more IP address inputs. Check if we have a consistent set if (!addrs.isEmpty()) { if (!ipv4_addrs.isEmpty() && !ipv6_addrs.isEmpty()) { throw new RuntimeException("all addresses have to be either IPv4 or IPv6: IPv4 addresses=" + ipv4_addrs + ", IPv6 addresses=" + ipv6_addrs); } return !ipv6_addrs.isEmpty()? StackType.IPv6 : StackType.IPv4; } return StackType.Unknown; } /* * A method which does the following: * - 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<String, Map<String,InetAddressInfo>> createInetAddressMap(List<ProtocolConfiguration> protocol_configs, List<Protocol> protocols) throws Exception { // Map protocol -> Map<String, InetAddressInfo>, where the latter is protocol specific Map<String, Map<String,InetAddressInfo>> inetAddressMap = new HashMap<>() ; // collect InetAddressInfo 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<String,String> properties = new HashMap<>(protocol_config.getProperties()); // check which InetAddress-related properties are ***non-null ***, and // create an InetAddressInfo structure for them // Method[] methods=protocol.getClass().getMethods(); Method[] methods=Util.getAllDeclaredMethodsWithAnnotations(protocol.getClass(), Property.class); for(int j = 0; j < methods.length; j++) { if (methods[j].isAnnotationPresent(Property.class) && isSetPropertyMethod(methods[j])) { 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); } catch(Exception e) { throw new Exception("String value 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<String,InetAddressInfo> 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 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)) { 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); } catch(Exception e) { throw new Exception("String value 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<String,InetAddressInfo> m=inetAddressMap.computeIfAbsent(protocolName, k -> new HashMap<>()); m.put(propertyName, inetinfo) ; }// recompute } } } } return inetAddressMap ; } public static List<InetAddress> getInetAddresses(List<Protocol> protocols) throws Exception { List<InetAddress> 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=getValueFromProtocol(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; } /* * Method which processes @Property.default() values, associated with the annotation * using the defaultValue= attribute. This method does the following: * - locate all properties which have no user value assigned * - if the defaultValue attribute is not "", generate a value for the field using the * property converter for that property and assign it to the field */ public static void setDefaultValues(List<ProtocolConfiguration> protocol_configs, List<Protocol> protocols, StackType ip_version) throws Exception { InetAddress default_ip_address=Util.getNonLoopbackAddress(); if(default_ip_address == null) { log.warn(Util.getMessage("OnlyLoopbackFound"), ip_version); default_ip_address=Util.getLocalhost(ip_version); } 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<String,String> properties=new HashMap<>(protocol_config.getProperties()); Method[] methods=Util.getAllDeclaredMethodsWithAnnotations(protocol.getClass(), Property.class); for(int j=0; j < methods.length; j++) { if(isSetPropertyMethod(methods[j])) { String propertyName=PropertyHelper.getPropertyName(methods[j]); Object propertyValue=getValueFromProtocol(protocol, propertyName); if(propertyValue == null) { // if propertyValue is null, check if there is a we can use Property annotation=methods[j].getAnnotation(Property.class); // get the default value for the method- check for InetAddress types String defaultValue=null; if(InetAddressInfo.isInetAddressRelated(methods[j])) { 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(protocol, methods[j], properties, defaultValue, true); methods[j].invoke(protocol, converted); } catch(Exception e) { throw new Exception("default could not be assigned for method " + propertyName + " in " + protocolName + " with default " + defaultValue, e); } log.debug("set property " + protocolName + "." + propertyName + " to default value " + converted); } } } } } //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); Object propertyValue=getValueFromProtocol(protocol, fields[j]); if(propertyValue == null) { // add to collection of @Properties with no user specified value Property annotation=fields[j].getAnnotation(Property.class); // get the default value for the field - check for InetAddress types String defaultValue=null; if(InetAddressInfo.isInetAddressRelated(fields[j])) { defaultValue=ip_version == StackType.IPv4? annotation.defaultValueIPv4() : annotation.defaultValueIPv6(); if(defaultValue != null && !defaultValue.isEmpty()) { // condition for invoking converter if(defaultValue != null || !PropertyHelper.usesDefaultConverter(fields[j])) { Object converted=null; try { if(defaultValue.equalsIgnoreCase(Global.NON_LOOPBACK_ADDRESS)) converted=default_ip_address; else converted=PropertyHelper.getConvertedValue(protocol, fields[j], properties, defaultValue, true); if(converted != null) Util.setField(fields[j], protocol, converted); } catch(Exception e) { throw new Exception("default could not be assigned for field " + propertyName + " in " + protocolName + " with default value " + defaultValue, e); } log.debug("set property " + protocolName + "." + propertyName + " to default value " + converted); } } } } } } } public static void setDefaultValues(List<Protocol> protocols, StackType ip_version) throws Exception { InetAddress default_ip_address=Util.getNonLoopbackAddress(); if(default_ip_address == null) { log.warn(Util.getMessage("OnlyLoopbackFound"), ip_version); default_ip_address=Util.getLocalhost(ip_version); } 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(), Property.class); for(int j=0; j < fields.length; j++) { // get the default value for the field - check for InetAddress types if(InetAddressInfo.isInetAddressRelated(fields[j])) { Object propertyValue=getValueFromProtocol(protocol, fields[j]); if(propertyValue == null) { // add to collection of @Properties with no user specified value Property annotation=fields[j].getAnnotation(Property.class); String defaultValue=ip_version == StackType.IPv4? annotation.defaultValueIPv4() : annotation.defaultValueIPv6(); if(defaultValue != null && !defaultValue.isEmpty()) { // condition for invoking converter Object converted=null; try { if(defaultValue.equalsIgnoreCase(Global.NON_LOOPBACK_ADDRESS)) converted=default_ip_address; else converted=PropertyHelper.getConvertedValue(protocol, fields[j], defaultValue, true); if(converted != null) Util.setField(fields[j], protocol, converted); } catch(Exception e) { throw new Exception("default could not be assigned for field " + fields[j].getName() + " in " + protocolName + " with default value " + defaultValue, e); } log.debug("set property " + protocolName + "." + fields[j].getName() + " 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 * @param protocols * @throws Exception */ public static void ensureValidBindAddresses(List<Protocol> 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=getValueFromProtocol(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 Object getValueFromProtocol(Protocol protocol, Field field) throws IllegalAccessException { if(protocol == null || field == null) return null; if(!Modifier.isPublic(field.getModifiers())) field.setAccessible(true); return field.get(protocol); } public static Object getValueFromProtocol(Protocol protocol, String field_name) throws IllegalAccessException { if(protocol == null || field_name == null) return null; Field field=Util.getField(protocol.getClass(), field_name); return field != null? getValueFromProtocol(protocol, 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<String,String> properties) { // List of Fields and Methods of the protocol annotated with @Property List<AccessibleObject> unorderedFieldsAndMethods = new LinkedList<>() ; List<AccessibleObject> orderedFieldsAndMethods = new LinkedList<>() ; // Maps property name to property object Map<String, AccessibleObject> 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])) { 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<AccessibleObject> orderFieldsAndMethodsByDependency(List<AccessibleObject> unorderedList, Map<String, AccessibleObject> propertiesMap) { // Stack to detect cycle in depends relation Stack<AccessibleObject> stack = new Stack<>() ; // the result list List<AccessibleObject> 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<AccessibleObject> orderedList, Map<String, AccessibleObject> props, Stack<AccessibleObject> stack, AccessibleObject obj) { if (orderedList.contains(obj)) return ; if (stack.search(obj) > 0) { 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<AccessibleObject> objects, Map<String, AccessibleObject> props) { // iterate overall properties marked by @Property for(int i = 0; i < objects.size(); i++) { // 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.toString()) ; } 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<String> keyset = props.keySet(); for (Iterator<String> 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<String,String> props) throws Exception { Method[] methods=obj.getClass().getMethods(); for(Method method: methods) { resolveAndInvokePropertyMethod(obj, method, props) ; } } public static void resolveAndInvokePropertyMethod(Object obj, Method method, Map<String,String> props) throws Exception { String methodName=method.getName(); Property annotation=method.getAnnotation(Property.class); if(annotation != null && isSetPropertyMethod(method)) { 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); 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<String,String> props) 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) ; } } public static void resolveAndAssignField(Object obj, Field field, Map<String,String> props) 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); 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 void removeDeprecatedProperties(Object obj, Map<String,String> props) throws Exception { //traverse class hierarchy and find all deprecated properties for(Class<?> clazz=obj.getClass(); clazz != null; clazz=clazz.getSuperclass()) { if(clazz.isAnnotationPresent(DeprecatedProperty.class)) { DeprecatedProperty declaredAnnotation=clazz.getAnnotation(DeprecatedProperty.class); String[] deprecatedProperties=declaredAnnotation.names(); for(String propertyName : deprecatedProperties) { String propertyValue=props.get(propertyName); if(propertyValue != null) { if(log.isWarnEnabled()) { String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); log.warn(Util.getMessage("Deprecated"), name + "." + propertyName, "will be ignored"); } props.remove(propertyName); } } } } } public static boolean isSetPropertyMethod(Method method) { return (method.getName().startsWith("set") && method.getReturnType() == java.lang.Void.TYPE && 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<String,String> properties ; String propertyName ; String stringValue ; Object convertedValue ; boolean isField ; boolean isParameterized ; // is the associated type parametrized? (e.g. Collection<String>) Object baseType ; // what is the base type (e.g. Collection) InetAddressInfo(Protocol protocol, AccessibleObject fieldOrMethod, Map<String,String> 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<InetAddress>, List<InetSocketAddress>, List<IpAddress> 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<InetAddress>, List<IpAddress> or List<InetSocketAddress> */ 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) ; } } static boolean isInetAddressOrCompatibleType(Class<?> c) { return c.equals(InetAddress.class) || c.equals(InetSocketAddress.class) || c.equals(IpAddress.class) ; } /* * Check if the parameterized type represents one of: * List<InetAddress>, List<IpAddress>, List<InetSocketAddress> */ 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<?> && rawType.equals(List.class))) { throw new IllegalArgumentException("Invalid parameterized type definition - parameterized type must be a List") ; } // 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<InetAddress> getInetAddresses() { List<InetAddress> addresses = new ArrayList<>() ; if (getConvertedValue() == null) return addresses ; // if we take only an InetAddress argument if (!isParameterized()) { addresses.add(getInetAddress(getConvertedValue())) ; return addresses ; } // if we take a List<InetAddress> 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(); } } }