/*
* 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.opstring;
import org.rioproject.associations.AssociationDescriptor;
import org.rioproject.deploy.StagedData;
import org.rioproject.deploy.SystemComponent;
import org.rioproject.exec.ExecDescriptor;
import org.rioproject.resolver.RemoteRepository;
import org.rioproject.sla.RuleMap;
import org.rioproject.sla.ServiceLevelAgreements;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
/**
* The ServiceElement object provides context on how to provision and instantiate
* services in the Rio architecture.
*
* @author Dennis Reedy
*/
public class ServiceElement implements Serializable {
@SuppressWarnings("unused")
static final long serialVersionUID = 1L;
public enum ProvisionType {
/**
* The EXTERNAL type indicates that the ServiceElement is not a
* provisionable component and must be instantiated using external
* mechanisms */
EXTERNAL,
/**
* The DYNAMIC type indicates that the ServiceElement will be
* provisioned to available ServiceBeanInstantiator instances that
* support the service's operational criteria up to the amount
* specified by the number of planned instances. */
DYNAMIC,
/**
* The FIXED type indicates that the ServiceElement will be provisioned
* to <i>every</i> ServiceBeanInstantiator instance that supports the
* ServiceBean's operational criteria */
FIXED}
/** Provision type, default to DYNAMIC */
private ProvisionType provisionType = ProvisionType.DYNAMIC;
/** The ServiceBeanConfig, providing configuration information for the
* ServiceBean */
private ServiceBeanConfig sbConfig;
/** Array of AssociationDescriptor instances, describing associations the
* ServiceElement has to other services */
private final List<AssociationDescriptor> associations = new ArrayList<AssociationDescriptor>();
/** The ClassBundle for the ServiceBean */
private ClassBundle componentBundle;
/** Array of ClassBundles containing the export codebase */
private ClassBundle[] exportBundles = new ClassBundle[0];
/** Collection of provisionable PlatformCapability objects */
private final Collection<SystemComponent> provisionableCapabilities = new ArrayList<SystemComponent>();
/** The ServiceLevelAgreements object defines system and service level
* objectives that are to be monitored, metered and acted on by policy
* handlers */
private ServiceLevelAgreements slAgreements;
/** Use the name in addition to public interfaces to track the service */
private boolean matchOnName = true;
/** Whether to automatically advertise the service as part of service
* instantiation */
private boolean autoAdvertise = true;
/**
* Whether to acquire the DiscoveryManagement for the service from a shared
* pool of DiscoveryManagement instances
*/
private boolean discoPool = true;
/** Number of planned instances */
private int planned;
/** Maximum number of services per machine. Setting to -1 means no maximum */
private int maxPerMachine = -1;
public enum MachineBoundary {
/**
* Declares that the machine boundary between service instances is at
* the JVM */
VIRTUAL,
/**
* Declares that the machine boundary between service instances is at
* the physical (machine) level */
PHYSICAL
}
/**
* The machine boundary property, only used if the maxPerMachine property
* is set
*/
private MachineBoundary machineBoundary=MachineBoundary.VIRTUAL;
/** Number of actual instances */
private int actual;
/** Array of machines that have been identified as part of a cluster of
* machine used as targets for provisioning */
private String[] machineCluster;
/** FaultDetectionHandler ClassBundle */
private ClassBundle fdhBundle;
/** ExecDescriptor, providing attributes for an external service to execute */
private ExecDescriptor execDescriptor;
/** Collection of artifacts to download for the service */
private final List<StagedData> stagedData = new ArrayList<StagedData>();
/** Whether the service executes in it's own JVM */
private boolean fork = false;
private final List<RemoteRepository> remoteRepositories = new ArrayList<RemoteRepository>();
private final List<RuleMap> ruleMaps = new ArrayList<RuleMap>();
/**
* Construct a ServiceElement
*/
public ServiceElement() {
this(ProvisionType.DYNAMIC);
}
/**
* Construct a ServiceElement
*
* @param provisionType The {@link ProvisionType} for the ServiceElement
*/
public ServiceElement(ProvisionType provisionType) {
this.provisionType = provisionType;
this.sbConfig = new ServiceBeanConfig();
}
/**
* Construct a ServiceElement
*
* @param provisionType The {@link ProvisionType} for the ServiceElement
* @param sbConfig The ServiceBeanConfig to set
* @param slAgreements Attributes relating to SLAs
* @param exports ClassBundle[] of JARs to use as the export codebase
*/
public ServiceElement(ProvisionType provisionType,
ServiceBeanConfig sbConfig,
ServiceLevelAgreements slAgreements,
ClassBundle[] exports) {
this(provisionType, sbConfig, slAgreements, exports, null, null);
}
/**
* Construct a ServiceElement
*
* @param provisionType The {@link ProvisionType} for the ServiceElement
* @param sbConfig The ServiceBeanConfig to set
* @param slAgreements Attributes relating to SLAs
* @param exports ClassBundle[] of JARs to use as the export codebase
* @param fdhBundle ClassBundle for the FaultDetectionHandler
* @param componentBundle The ClassBundle for the ServiceBean component
*/
public ServiceElement(ProvisionType provisionType,
ServiceBeanConfig sbConfig,
ServiceLevelAgreements slAgreements,
ClassBundle[] exports,
/* Optional args */
ClassBundle fdhBundle,
ClassBundle componentBundle) {
if(sbConfig==null)
throw new IllegalArgumentException("sbConfig is null");
if(exports == null)
throw new IllegalArgumentException("exports is null");
this.fdhBundle = fdhBundle;
this.provisionType = provisionType;
exportBundles = new ClassBundle[exports.length];
System.arraycopy(exports, 0, exportBundles, 0, exports.length);
this.componentBundle = componentBundle;
this.sbConfig = sbConfig;
this.slAgreements = slAgreements;
}
/**
* Set the provision type set for this service.
*
* @param provisionType The provision type
*/
public void setProvisionType(ProvisionType provisionType) {
this.provisionType = provisionType;
}
/**
* Get the provision type set for this service.
*
* @return The provision type
*/
public ProvisionType getProvisionType() {
return provisionType;
}
/**
* Get the name of the ServiceElement
*
* @return The name of the ServiceElement
*/
public String getName() {
if(sbConfig==null)
return null;
return(sbConfig.getName());
}
/**
* Set the number of instances of this service that should exist on the
* network.
*
* @param planned The number of planned instances
*
* @throws IllegalArgumentException if planned is less then 0
*/
public void setPlanned(int planned) {
if(planned<0)
throw new IllegalArgumentException("planned cannot be < 0");
this.planned = planned;
}
/**
* Increment the number of instances of this service that should exist on
* the network by one
*/
public void incrementPlanned() {
planned++;
}
/**
* Decrement the number of instances of this service that should exist on
* the network by one. If the planned attribute is zero, this operational
* does nothing
*/
public void decrementPlanned() {
if(planned > 0)
planned--;
}
/**
* Get the number of instances of this service that should exist on the
* network.
*
* @return int The number of instances of this service that should exist on
* the network
*/
public int getPlanned() {
return planned;
}
/**
* Set the actual
*
* @param actual The amount of services discovered
*/
public void setActual(int actual) {
this.actual = actual;
}
/**
* Get the actual amount of services discovered
*
* @return The actualt number of discovered instances
*/
public int getActual() {
return actual;
}
/**
* Set the maximum number of instances of this service that should exist on
* any given machine
*
* @param maxPerMachine The maximum number of service that should exist on
* any given machine
*
* @throws IllegalArgumentException if maxPerMachine is less then -1
*/
public void setMaxPerMachine(int maxPerMachine) {
if(maxPerMachine < -1)
throw new IllegalArgumentException("maxPerMachine cannot be less then -1");
this.maxPerMachine = maxPerMachine;
}
/**
* Get the maximum number of instances of this service that should exist on
* any given machine
*
* @return The maximum number of service that should exist on any
* given machine. If the maxPerMachine value has not been set, the value
* will be -1
*/
public int getMaxPerMachine() {
return maxPerMachine;
}
/**
* Set the machine boundary. This property, when used in conjunction with
* the {@link ServiceElement#maxPerMachine} property, sets the boundary
* affinity between servce instances to be either at the virtual machine or
* the physical machine
*
* @param boundary The machine boundary.
*/
public void setMachineBoundary(MachineBoundary boundary) {
machineBoundary = boundary;
}
/**
* Get the machine boundary
*
* @return The machine boundary. Only used in conjunction with
* the {@link ServiceElement#maxPerMachine} property
*/
public MachineBoundary getMachineBoundary() {
return machineBoundary;
}
/**
* Set the machineCluster property
*
* @param machineCluster The cluster of targeted machine(s) to provision
* the ServiceBean to. Array elements should be either the hostname or an IP
* Address of a machine.
*/
public void setCluster(String... machineCluster) {
this.machineCluster = machineCluster;
}
/**
* Get the cluster of targeted machine(s) to provision the ServiceBean to.
* Array elements will be either the hostname or an IP Address of a machine.
*
* @return An array of machines to provision the ServiceBean to
*/
public String[] getCluster() {
if(machineCluster == null)
return new String[0];
String[] cluster = new String[machineCluster.length];
System.arraycopy(machineCluster, 0, cluster, 0, machineCluster.length);
return cluster;
}
/**
* Set the FaultDetectionHandler ClassBundle
*
* @param fdhBundle The ClassBundle providing attributes on the
* FaultDetectionHandler to use
*/
public void setFaultDetectionHandlerBundle(ClassBundle fdhBundle) {
this.fdhBundle = fdhBundle;
}
/**
* Get the FaultDetectionHandler ClassBundle
*
* @return The ClassBundle providing attributes on the
* FaultDetectionHandler to use
*/
public ClassBundle getFaultDetectionHandlerBundle() {
return fdhBundle;
}
/**
* This method sets the instance of <code>ServiceBeanConfig</code> as
* an attribute of the <code>ServiceElement</code> object.
*
* @param sbConfig The ServiceBeanConfig to set
*/
public void setServiceBeanConfig(ServiceBeanConfig sbConfig) {
if(sbConfig==null)
throw new IllegalArgumentException("sbConfig is null");
this.sbConfig = sbConfig;
}
/**
* This method returns an instance of the <code>ServiceBeanConfig</code>
* object created for this service as part of this Operational String<br>
*
* @return The ServiceBeanConfig object for this ServiceElement
*/
public ServiceBeanConfig getServiceBeanConfig() {
return sbConfig;
}
/**
* Get the name of the OperationalString
*
* @return Name of the OperationalString
*/
public String getOperationalStringName() {
if(sbConfig==null)
return null;
return sbConfig.getOperationalStringName();
}
/**
* Set the name of the OperationalString. This will also set the
* OperationalString name into
* {@link org.rioproject.associations.AssociationDescriptor}s that are set
* to discover intra-OperationalString services
*
* @param name Name of the OperationalString
*
* @throws IllegalArgumentException if the name is null
*/
public void setOperationalStringName(String name) {
if(name==null)
throw new IllegalArgumentException("name cannot be null");
String oldName = getOperationalStringName();
sbConfig.setOperationalStringName(name);
for(AssociationDescriptor aDesc : getAssociationDescriptors()) {
if(aDesc.getOperationalStringName()!=null &&
aDesc.getOperationalStringName().equals(oldName))
aDesc.setOperationalStringName(name);
}
}
/**
* Set whether to automatically advertise the service as part of service
* instantiation
*
* @param autoAdvertise If true, automatically advertise the ServiceBean when
* instantiated
*/
public void setAutoAdvertise(boolean autoAdvertise) {
this.autoAdvertise = autoAdvertise;
}
/**
* Get whether to automatically advertise the service as part of service
* instantiation
*
* @return If true, automatically advertise the ServiceBean when
* instantiated
*/
public boolean getAutoAdvertise() {
return autoAdvertise;
}
/**
* Set whether to acquire the DiscoveryManagement for the service from a shared
* pool of DiscoveryManagement instances. Sharing DiscoveryManagement instances
* results in optimizing the creation of DiscoveryManagement instances
*
* <p>Note: Use of shared (pooled) DiscoveryManagement instances must be used with
* care as changing the settings of the returned DiscoveryManagement instance may
* present problems for other users of DiscoveryManagement instance and is not
* advised.
*
* @param discoPool If true, get the DiscoveryManagement instance for the
* service from a pool of available DiscoveryManagement instances
*/
public void setDiscoveryManagementPooling(boolean discoPool) {
this.discoPool = discoPool;
}
/**
* Get whether to acquire the DiscoveryManagement for the service from a shared
* pool of DiscoveryManagement instances. Sharing DiscoveryManagement instances
* results in optimizing the creation of DiscoveryManagement instances
*
* <p>Note: Use of shared (pooled) DiscoveryManagement instances must be used with
* care as changing the settings of the returned DiscoveryManagement instance may
* present problems for other users of DiscoveryManagement instance and is not
* advised.
*
* @return If true, get the DiscoveryManagement instance for the service from a
* pool of available DiscoveryManagement instances
*/
public boolean getDiscoveryManagementPooling() {
return discoPool;
}
/**
* Set whether to use the name of the service is used in addition to the
* interfaces implemented by the service or service proxy to track service
* instances.
*
* @param matchOnName If true to use the name returned by the
* <code>getName</code> method in addition to the interfaces implemented by
* the service or service proxy to track service instances.
*/
public void setMatchOnName(boolean matchOnName) {
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 boolean true to use the name returned by the <code>getName</code>
* method
*/
public boolean getMatchOnName() {
return matchOnName;
}
/**
* Set the component ClassBundle
*
* @param componentBundle The ClassBundle identifying the class and required
* resources to load the ServiceBean
*/
public void setComponentBundle(ClassBundle componentBundle) {
this.componentBundle = componentBundle;
}
/**
* Get the component ClassBundle
*
* @return The ClassBundle identifying the class and required resources to
* load the ServiceBean
*/
public ClassBundle getComponentBundle() {
return componentBundle;
}
/**
* Set the the ClassBundle objects for the public interfaces the
* service implements
*
* @param exports ClassBundle objects for the public interfaces the
* service implements
*/
public void setExportBundles(ClassBundle... exports) {
this.exportBundles = new ClassBundle[exports.length];
System.arraycopy(exports, 0, exportBundles, 0, exports.length);
}
/**
* Get the the Array of ClassBundle objects for the public interfaces the
* service implements
*
* @return Array of ClassBundle objects for the public interfaces the
* service implements
*/
public ClassBundle[] getExportBundles() {
ClassBundle[] bundle;
if(exportBundles!=null) {
bundle = new ClassBundle[exportBundles.length];
System.arraycopy(exportBundles, 0, bundle, 0, exportBundles.length);
} else {
bundle = new ClassBundle[0];
}
return bundle;
}
/**
* Get the export URLs. Helper method to get the array of URLs from the
* array of ClassBundle objects representing the export class(es) and search paths
* for those classes
*
* @return Array of URLs of the export classes. A new array is allocated each
* time this method is called
*
* @throws MalformedURLException If the URLs are incorrect
*/
public URL[] getExportURLs() throws MalformedURLException {
ArrayList<URL> list = new ArrayList<URL>();
if(exportBundles!=null) {
for (ClassBundle exportBundle : exportBundles) {
if(exportBundle.getCodebase()!=null) {
URL[] urls = exportBundle.getJARs();
list.addAll(Arrays.asList(urls));
}
}
}
return list.toArray(new URL[list.size()]);
}
/**
* Set the ServiceLevelAgreements object.
*
* @param slaAgreements The ServiceLevelAgreements object defines system and
* service level objectives that are to be monitored, metered and acted on
* by policy handlers
*/
public void setServiceLevelAgreements(ServiceLevelAgreements slaAgreements) {
this.slAgreements = slaAgreements;
}
/**
* Returns the ServiceLevelAgreements object.
*
* @return The ServiceLevelAgreements object defines system and service level
* objectives that are to be monitored, metered and acted on by policy handlers
*/
public ServiceLevelAgreements getServiceLevelAgreements() {
if(slAgreements==null)
slAgreements=new ServiceLevelAgreements();
return slAgreements;
}
/**
* Set {@code AssociationDescriptor} for the service
*
* @param associationDescriptors {@code AssociationDescriptor} to set
*/
public void setAssociationDescriptors(AssociationDescriptor... associationDescriptors) {
associations.clear();
if(associationDescriptors!=null) {
Collections.addAll(associations, associationDescriptors);
}
}
/**
* Add {@code AssociationDescriptor} for the service
*
* @param associationDescriptors {@code AssociationDescriptor} to add
*/
public void addAssociationDescriptors(AssociationDescriptor... associationDescriptors) {
if(associationDescriptors!=null) {
Collections.addAll(associations, associationDescriptors);
}
}
/**
* Get the associations for the service
*
* @return Array of AssociationDescriptor objects. If there are no
* AssociationDescriptor objects, this method returns a zero-length array
*/
public AssociationDescriptor[] getAssociationDescriptors() {
return associations.toArray(new AssociationDescriptor[associations.size()]);
}
/**
* Set the SystemRequirement downloads for a resource.
*
* @param downloadableCapabilities The downloadableCapabilities
*/
public void setProvisionablePlatformCapabilities(
Collection<SystemComponent> downloadableCapabilities) {
synchronized(provisionableCapabilities) {
provisionableCapabilities.clear();
provisionableCapabilities.addAll(downloadableCapabilities);
}
}
/**
* Get the provisionable SystemRequirement instances
*
* @return A Collection of SystemRequirement instances which
* need to be provisioned in order for the ServiceBean to be instantiated. A
* new Collection is created each time this method is called. If there are
* no provisionable SystemRequirement instances, this method returns an
* empty Collection.
*/
public Collection<SystemComponent> getProvisionablePlatformCapabilities() {
Collection<SystemComponent> collection = new ArrayList<SystemComponent>();
synchronized(provisionableCapabilities) {
collection.addAll(provisionableCapabilities);
}
return collection;
}
/**
* Get the execution descriptor
*
* @return The ExecDescriptor, providing attributes for an external
* service to execute. May be null.
*/
public ExecDescriptor getExecDescriptor() {
return execDescriptor;
}
/**
* Set the execution descriptor
*
* @param execDescriptor The ExecDescriptor, providing attributes for an
* external service to execute.
*/
public void setExecDescriptor(ExecDescriptor execDescriptor) {
this.execDescriptor = execDescriptor;
}
/**
* Set StagedData for the service
*
* @param stagedData StagedData to set. If null, the method does
* nothing
*/
public void setStagedData(StagedData... stagedData) {
if(stagedData !=null)
this.stagedData.addAll(Arrays.asList(stagedData));
}
/**
* Get the StagedData for the service
*
* @return An array of StagedData for the service. If there is no
* StagedData for the service, a zero-length array is returned.
*/
public StagedData[] getStagedData() {
return stagedData.toArray(new StagedData[stagedData.size()]);
}
/**
* Get whether the service executes in it's own JVM
*
* @return True if the service is to be forked in it's own JVM, false
* otherwise.
*/
public boolean forkService() {
return fork;
}
/**
* Set whether the service executes in it's own JVM
*
* @param fork If true, the service will be created in it's own JVM
*/
public void setFork(boolean fork) {
this.fork = fork;
}
public RemoteRepository[] getRemoteRepositories() {
return remoteRepositories.toArray(new RemoteRepository[remoteRepositories.size()]);
}
public void setRemoteRepositories(Collection<RemoteRepository> remoteRepositories) {
if(remoteRepositories!=null)
this.remoteRepositories.addAll(remoteRepositories);
}
public List<RuleMap> getRuleMaps() {
return ruleMaps;
}
public void setRuleMaps(Collection<RuleMap> ruleMaps) {
this.ruleMaps.addAll(ruleMaps);
}
/**
* Override hashCode
*/
public int hashCode() {
int hc = 17;
hc = 37*hc+getName().hashCode();
hc = 37*hc+getOperationalStringName().hashCode();
for (ClassBundle exportBundle : exportBundles)
hc = 37 * hc + exportBundle.hashCode();
hc = 37*hc+(componentBundle==null?0:componentBundle.hashCode());
return(hc);
}
/**
* Override equals
*/
public boolean equals(Object obj) {
if(this == obj)
return true;
if(!(obj instanceof ServiceElement)) {
return false;
}
ServiceElement that = (ServiceElement)obj;
if(this.getName().equals(that.getName()) &&
this.getOperationalStringName().equals(that.getOperationalStringName())) {
/* If they exportBundles are different lengths, then the ServiceElements
* are not equal */
if(this.exportBundles.length!=that.exportBundles.length) {
return false;
}
/* The ServiceElements should have the same export jar names */
for (ClassBundle exportBundle1 : this.exportBundles) {
boolean matched = false;
for (ClassBundle exportBundle : that.exportBundles) {
if (exportBundle1.equals(exportBundle))
matched = true;
}
if (!matched) {
return false;
}
}
/* If we've matched the exports and both ServiceElements dont have
* components bundles they are the same */
if(this.componentBundle==null && that.componentBundle==null)
return true;
/* If the ServiceElements do have components, make sure the component
* jar names are equal */
if(this.componentBundle!=null && that.componentBundle!=null) {
return this.componentBundle.equals(that.componentBundle);
}
}
return false;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("ServiceElement");
sb.append("{provisionType=").append(provisionType);
sb.append(", sbConfig=").append(sbConfig);
sb.append(", associations=").append(associations);
sb.append(", componentBundle=").append(componentBundle);
sb.append(", exportBundles=").append(exportBundles == null? "null": Arrays.asList(exportBundles).toString());
sb.append(", provisionableCapabilities=").append(provisionableCapabilities);
sb.append(", slAgreements=").append(slAgreements);
sb.append(", matchOnName=").append(matchOnName);
sb.append(", autoAdvertise=").append(autoAdvertise);
sb.append(", discoPool=").append(discoPool);
sb.append(", planned=").append(planned);
sb.append(", maxPerMachine=").append(maxPerMachine);
sb.append(", machineBoundary=").append(machineBoundary);
sb.append(", actual=").append(actual);
sb.append(", machineCluster=").append(machineCluster == null? "null": Arrays.asList(machineCluster).toString());
sb.append(", fdhBundle=").append(fdhBundle);
sb.append(", execDescriptor=").append(execDescriptor);
sb.append(", stagedData=").append(stagedData);
sb.append(", fork=").append(fork);
sb.append('}');
return sb.toString();
}
}