/*
* Copyright to the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.rioproject.associations;
import net.jini.core.discovery.LookupLocator;
import org.rioproject.opstring.ClassBundle;
import org.rioproject.opstring.ServiceBeanConfig;
import java.io.Serializable;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* The AssociationDescriptor defines the attributes needed to create, manage and
* monitor {@code Association} instances.
*
* <br>
* When setting the {@code version} property, behavior is as follows. The
* {@code version} property value can use the asterisk (*) or plus (+) symbols to
* determine version matching. Version matching is done using service published {@link org.rioproject.entry.VersionEntry}
* attributes <br>
* <br>
* <table cellpadding="2" cellspacing="2" border="1"
* style="text-align: left; width: 100%;">
* <tbody>
* <tr>
* <th style="vertical-align: top;">Requirement<br>
* </th>
* <th style="vertical-align: top;">Support Criteria<br>
* </th>
* </tr>
* <tr>
* <td style="vertical-align: top;">1.2.7<br>
* </td>
* <td style="vertical-align: top;">Specifies an exact version<br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top;">2*<br>
* </td>
* <td style="vertical-align: top;">Supported for all minor versions of 2 <br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top;">3.4*<br>
* </td>
* <td style="vertical-align: top;">Supported for all minor versions of 3.4, including 3.4<br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top;">1.2+<br>
* </td>
* <td style="vertical-align: top;">Supported for version 1.2 or
* above </td>
* </tr>
* </tbody>
* </table>
* <br>
* Version requirements are expected to be a "." separated String of
* integers. Character values are ignored. For example; a version
* declaration of "2.0-M3" will be processed as "2.0.0.3"<br>
*
* @author Dennis Reedy
*/
public class AssociationDescriptor implements Serializable {
private static final long serialVersionUID = 1L;
/**
* If an AssociationDescriptor is created with a null for the name
* property, the AssociationDescriptor name property will be set to this
* value
*/
public static final String NO_NAME="<no-name>";
/**
* The AssociationDescriptor type
*/
private AssociationType type;
/**
* Array of public interface names the associated service implements
*/
private String[] interfaceNames;
/**
* Name of the associated service
*/
private String name;
/**
* Name of the associated service's OperationalString
*/
private String opStringName;
/**
* The FaultDetectionHandler ClassBundle
*/
private ClassBundle fdhBundle;
/**
* Associated service discovery groups
*/
private String[] groups;
/**
* Associated service discovery locators
*/
private LookupLocator[] lookupLocators;
/**
* Use the name in addition to public interfaces to track the service
*/
private boolean matchOnName = false;
/**
* The property to set when the associated service is discovered, changed
* or broken. This provides support for association based dependency
* injection
*/
private String propertyName;
/**
* The proxy to create
*/
private String proxyClass;
/**
* The {@code AssociationMatchFilter}
*/
private AssociationMatchFilter associationMatchFilter;
/**
* The ServiceSelectionStrategy class
*/
private String serviceStrategyClass;
/**
* Whether to lazily inject (inject when a service is discovered),
* or to eagerly inject (inject immediately)
*/
private boolean lazyInject = true;
/**
* The timeout value for service discovery
*/
private long serviceDiscoveryTimeout;
/**
* The number of units service discovery timeout is for
*/
private TimeUnit serviceDiscoveryTimeUnits = TimeUnit.MINUTES;
/**
* Version to match
*/
private String version;
/**
* Create an AssociationDescriptor
*/
public AssociationDescriptor() {
this(AssociationType.USES);
}
/**
* Create an AssociationDescriptor
*
* @param type The AssociationType
*/
public AssociationDescriptor(final AssociationType type) {
this(type, null, null, null);
}
/**
* Create an AssociationDescriptor
*
* @param type The AssociationType
* @param name The name of the associated service, may be <code>null</code>
*/
public AssociationDescriptor(final AssociationType type, final String name) {
this(type, name, null, null);
}
/**
* Create an AssociationDescriptor
*
* @param type The AssociationType
* @param name The name of the associated service
* @param opStringName The name of the OperationalString the
* associated service is part of, may be <code>null</code>
* @param propertyName The property to set when the associated service is
* discovered, changed or broken. May be <code>null</code>
*/
public AssociationDescriptor(final AssociationType type,
final String name,
final String opStringName,
final String propertyName) {
if(type==null)
throw new IllegalArgumentException("type is null");
this.type = type;
this.name = name;
this.opStringName = opStringName;
this.propertyName = propertyName;
}
/**
* Get the AssociationDescriptor type
*
* @return The AssociationType
*/
public AssociationType getAssociationType() {
return (type);
}
/**
* Set the propertyName
*
* @param propertyName The propertyName to use to inject the Association
*/
public void setPropertyName(final String propertyName) {
this.propertyName = propertyName;
}
/**
* Get the propertyName
*
* @return The propertyName to set when the associated service is
* discovered, changed or broken. This provides support for association
* based dependency injection. If the propertyName is null, dependency
* injection will not be used with this association
*/
public String getPropertyName() {
return(propertyName);
}
/**
* Set the associated service's name
*
* @param name The associated service's name.
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the associated service's name
*
* @return The associated service's name. If the name is <tt>null</tt>, the
* {@link AssociationDescriptor#NO_NAME} value is returned
*/
public String getName() {
return (name==null?NO_NAME:name);
}
/**
* Set the matchOnName property
*
* @param matchOnName Whether the association should match on the service
* name.
*
* @throws IllegalArgumentException If the AssociationDescriptor has it's
* name property set to null and the matchOnName value is true
*/
public void setMatchOnName(final boolean matchOnName) {
if(matchOnName && name==null)
throw new IllegalArgumentException("cannot match on a null name");
this.matchOnName = matchOnName;
}
/**
* If this method returns true then the name of the service is used in
* addition to the interfaces implemented by the service or service proxy
* to track service instances. If this method returns false, then only the
* interfaces will be used.
*
* @return True to use the name returned by the <code>getName</code>
* method
*/
public boolean matchOnName() {
return (matchOnName);
}
/**
* Set the Array of public interface names the associated service
* implements
*
* @param interfaces Array of public interface names the associated
* service implements
*/
public void setInterfaceNames(final String... interfaces) {
if(interfaces == null)
return;
this.interfaceNames = new String[interfaces.length];
System.arraycopy(interfaces, 0, interfaceNames, 0, interfaceNames.length);
}
/**
* Get the the Array of public interfaces the associated service implements
*
* @return Array of public interfaces names the
* associated service implements. If there are no public interfaces this
* method will return a zero-length array
*/
public String[] getInterfaceNames() {
if(interfaceNames==null)
return(new String[0]);
String[] iNames = new String[interfaceNames.length];
System.arraycopy(interfaceNames, 0, iNames, 0, interfaceNames.length);
return (iNames);
}
/**
* Get the associated service's OperationalString name
*
* @return The associated service's OperationalString name
*/
public String getOperationalStringName() {
return (opStringName);
}
/**
* Set the associated service's OperationalString name
*
* @param opStringName The associated service's
* OperationalString name
*/
public void setOperationalStringName(final String opStringName) {
this.opStringName = opStringName;
}
/**
* Set the <code>FaultDetectionHandler</code> ClassBundle
*
* @param fdhBundle The {@link org.rioproject.opstring.ClassBundle} used to
* create the fault detection handler
*/
public void setFaultDetectionHandlerBundle(final ClassBundle fdhBundle) {
this.fdhBundle = fdhBundle;
}
/**
* Get a <code>FaultDetectionHandler</code> from the Configuration
*
* @return The ClassBundle providing attributes on the
* FaultDetectionHandler to use
*/
public ClassBundle getFaultDetectionHandlerBundle() {
return (fdhBundle);
}
/**
* Set the Lookup groups the used to discover the associated service. If
* this parameter is not set (remains null), then no attempts will be
* made via group discovery to discover lookup services the associated
* service has registered
*
* @param groups Array of String group names whose members are
* the lookup services to discover. Elements contained within the array may
* be modified as follows:
*
* <ul>
* <li>If the groups property is equivalent to the value of
* LookupDiscovery.ALL_GROUPS, the value will be transformed into "all"
* <li>If groups property is equivalent to an empty string "", then the
* value will be transformed to "public"
* </ul>
*/
public void setGroups(final String... groups) {
if(groups == ServiceBeanConfig.ALL_GROUPS)
this.groups = new String[]{"all"};
else {
this.groups = new String[groups.length];
System.arraycopy(groups, 0, this.groups, 0, groups.length);
for(int i=0; i<this.groups.length; i++) {
if(this.groups[i].equals(""))
this.groups[i] = "public";
}
}
}
/**
* This method provides the ability to set the array of locators that this
* ServiceBean would like to join. If this parameter is not set, then no
* attempts will be made via unicast discovery
*
* @param lookupLocators Array of LookupLocator instances
*/
public void setLocators(final LookupLocator... lookupLocators) {
this.lookupLocators = lookupLocators;
}
/**
* Returns an array consisting of the names of the groups whose members are
* the lookup services to discover.
* <ul>
* <li>If the groups property is null or has a zero length, then the
* returned value is LookupDiscovery.NO_GROUPS
* <li>If an element has the value of "all", that value will be transformed
* to LookupDiscovery.ALL_GROUPS
* <li>If an element has the value of "public", that value is transformed
* to an empty string ""
* </ul>
*
* @return String array of groups
*/
public String[] getGroups() {
String[] adjustedGroups = groups;
if(groups == null || groups.length == 0)
adjustedGroups = ServiceBeanConfig.NO_GROUPS;
if(adjustedGroups.length > 0) {
if(adjustedGroups.length == 1 && adjustedGroups[0].equals("all")) {
adjustedGroups = ServiceBeanConfig.ALL_GROUPS;
} else {
for(int i = 0; i < adjustedGroups.length; i++) {
if(adjustedGroups[i].equals("public"))
adjustedGroups[i] = "";
}
}
}
return (adjustedGroups);
}
/**
* Returns an array consisting of instances of
* {@link net.jini.core.discovery.LookupLocator} in which each
* instance corresponds to a specific lookup service to discover
*
* @return An array of LookupLocator instances. If there are no
* LookupLocator instances, return a zero length array
*/
public LookupLocator[] getLocators() {
if(lookupLocators == null)
return (new LookupLocator[0]);
LookupLocator[] locators = new LookupLocator[lookupLocators.length];
System.arraycopy(lookupLocators, 0, locators, 0, lookupLocators.length);
return (locators);
}
/**
* Set the proxy factory
*
* @param proxyClass The class name of the <code>AssociationProxy</code> to use
*/
public void setProxyClass(final String proxyClass) {
this.proxyClass = proxyClass;
}
/**
* Get the proxy factory
*
* @return The classname of the <code>AssociationProxy</code> to use
*/
public String getProxyClass() {
return proxyClass;
}
/**
* Set the association match filter class name
*
* @param associationMatchFilter The classname of the <code>AssociationMatchFilter</code>
*/
public void setAssociationMatchFilter(final AssociationMatchFilter associationMatchFilter) {
this.associationMatchFilter = associationMatchFilter;
}
/**
* Get the association match filter class name
*
* @return The {@code AssociationMatchFilter}
*/
public AssociationMatchFilter getAssociationMatchFilter() {
return associationMatchFilter;
}
/**
* Set the <code>ServiceSelectionStrategy</code> to use when selecting associated services.
*
* @param serviceStrategyClass The classname of the ServiceSelectionStrategy.
* The ServiceSelectionStrategy must have a zero-arg constructor and
* implement <code>ServiceSelectionStrategy</code>
*/
public void setServiceSelectionStrategy(final String serviceStrategyClass) {
this.serviceStrategyClass = serviceStrategyClass;
}
/**
* Get the {@code org.rioproject.associations.ServiceSelectionStrategy}
* classname to use for selecting associated services.
*
* @return The classname of the ServiceSelectionStrategy to create.
* The ServiceSelectionStrategy must have a zero-arg constructor and
* implement {@code org.rioproject.associations.ServiceSelectionStrategy}
*/
public String getServiceSelectionStrategy() {
return serviceStrategyClass;
}
/**
* Get the association injection style
*
* @return If the association is to be injected, whether to inject the
* association lazily (when the service is discovered), or eagerly (inject
* immediately).
*/
public boolean isLazyInject() {
return lazyInject;
}
/**
* Set the association injection style
*
* @param lazyInject If true, the association will be injected when the
* service is discovered. If false the association will be injected
* immediately.
*/
public void setLazyInject(final boolean lazyInject) {
this.lazyInject = lazyInject;
}
/**
* Set the timeout for service discovery
*
* @param serviceDiscoveryTimeout The service discovery timeout.
*/
public void setServiceDiscoveryTimeout(final long serviceDiscoveryTimeout) {
this.serviceDiscoveryTimeout = serviceDiscoveryTimeout;
}
/**
* Get the service discovery timeout
*
* @return The value to use for the service discovery timeout
*/
public long getServiceDiscoveryTimeout() {
return serviceDiscoveryTimeout;
}
/**
* Set the unit of time for the service discovery timeout
*
* @param serviceDiscoveryTimeUnits The unit of time for the service
* discovery timeout
*/
public void setServiceDiscoveryTimeUnits(final TimeUnit serviceDiscoveryTimeUnits) {
if(serviceDiscoveryTimeUnits==null)
throw new IllegalArgumentException("serviceDiscoveryTimeUnits is null");
if(serviceDiscoveryTimeUnits.ordinal()<TimeUnit.MILLISECONDS.ordinal())
throw new IllegalArgumentException("serviceDiscoveryTimeUnits cannot be smaller then MILLISECONDS");
this.serviceDiscoveryTimeUnits = serviceDiscoveryTimeUnits;
}
/**
* Get the unit of time for the service discovery timeout
*
* @return The unit of time for the service discovery timeout
*/
public TimeUnit getServiceDiscoveryTimeUnits() {
return serviceDiscoveryTimeUnits;
}
/**
* Creates a "uses" AssociationDescriptor for a service, matching on the
* service name
*
* @param name The service name
* @param serviceClass The service's exported proxy class
* @param groups Discovery groups to use
*
* @return An AssociationDescriptor
*/
public static AssociationDescriptor create(final String name, final Class serviceClass, final String... groups) {
return create(name, null, serviceClass, AssociationType.USES, groups);
}
/**
* Creates a "uses" AssociationDescriptor for a service, matching on the
* service name
*
* @param name The service name
* @param setter The setter property to use when injecting
* @param serviceClass The service's exported proxy class
* @param groups Discovery groups to use
*
* @return An AssociationDescriptor
*/
public static AssociationDescriptor create(final String name, final String setter, final Class serviceClass, final String... groups) {
return create(name, setter, serviceClass, AssociationType.USES, groups);
}
/**
* Creates an AssociationDescriptor for a service, matching on the service name
*
* @param name The service name
* @param setter The setter property to use when injecting
* @param serviceClass The service's exported proxy class
* @param type The type of Association to create
* @param groups Discovery groups to use
*
* @return An AssociationDescriptor
*/
public static AssociationDescriptor create(final String name,
final String setter,
final Class serviceClass,
final AssociationType type,
final String... groups) {
AssociationDescriptor ad = new AssociationDescriptor(type, name);
ad.setInterfaceNames(serviceClass.getName());
if(setter!=null)
ad.setPropertyName(setter);
ad.setGroups(groups);
ad.setMatchOnName(true);
return ad;
}
/**
* Get the version to match.
*
* @return The version to match, may be {@code null}
*/
public String getVersion() {
return version;
}
/**
* Set the version to match.
*
* @param version The version to match
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Override hashCode to return the hashCode of the name, opStringName
* and {@link AssociationType} hashCodes
*/
public int hashCode() {
int hc = 17;
hc = 37*hc+type.hashCode();
hc = 37*hc+getName().hashCode();
if(opStringName!=null)
hc = 37*hc+opStringName.hashCode();
if(propertyName!=null)
hc = 37*hc+propertyName.hashCode();
if(interfaceNames !=null)
hc = Arrays.hashCode(interfaceNames);
return (hc);
}
/**
* An AssociationDescriptor is equal to another AssociationDescriptor if
* their name, opStringName and
* {@link AssociationType} attributes are equal
*/
public boolean equals(final Object obj) {
if(this == obj)
return true;
if(!(obj instanceof AssociationDescriptor))
return false;
AssociationDescriptor that = (AssociationDescriptor)obj;
if(this.type.equals(that.type) && this.getName().equals(that.getName())) {
boolean matched = false;
/* Check propertyName attributes */
if(this.opStringName!=null && that.opStringName!=null && this.opStringName.equals(that.opStringName)) {
matched = true;
}
if(this.opStringName==null && that.opStringName==null)
matched = true;
if(!matched)
return matched;
if (!Arrays.equals(this.interfaceNames, that.interfaceNames))
return false;
/* Check propertyName attributes */
if(this.propertyName!=null && that.propertyName!=null && this.propertyName.equals(that.propertyName)) {
return true;
}
if(this.propertyName==null && that.propertyName==null)
return true;
}
return (false);
}
/**
* Override toString
*/
public String toString() {
StringBuilder buffer = new StringBuilder();
if(interfaceNames!=null) {
for(int i=0; i<interfaceNames.length; i++) {
if(i>0)
buffer.append(", ");
buffer.append(interfaceNames[i]);
}
} else {
buffer.append("<null>");
}
String iFaces = buffer.toString();
buffer.delete(0, buffer.length());
if(groups==null) {
buffer.append("<null>");
} else {
for(int i=0; i<groups.length; i++) {
if(i>0)
buffer.append(", ");
buffer.append(groups[i]);
}
}
String gps = buffer.toString();
String fdh = "<null>";
if(fdhBundle!=null)
fdh = fdhBundle.getClassName();
String ops="<null>";
if(opStringName!=null)
ops = opStringName;
return("Type="+type.toString()+", "+
"Name="+getName()+", "+
"Interfaces="+iFaces+", "+
"Groups="+gps+", "+
"Version="+version+", "+
"MatchOnName="+matchOnName+", "+
"OperationalString="+ops+", "+
"Property="+propertyName+", "+
"associationMatchFilter="+associationMatchFilter+", "+
"proxyClass="+proxyClass+", "+
"serviceStrategyClass="+serviceStrategyClass+", "+
"lazyInject="+lazyInject+", "+
"serviceDiscoveryTimeout="+serviceDiscoveryTimeout+", "+
"serviceDiscoveryTimeUnits="+serviceDiscoveryTimeUnits+", "+
"FDH="+fdh);
}
}