/*
* Copyright 2008 the original author or authors.
* Copyright 2005 Sun Microsystems, Inc.
*
* 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.impl.associations;
import net.jini.core.lookup.ServiceItem;
import org.rioproject.associations.Association;
import org.rioproject.associations.AssociationDescriptor;
import org.rioproject.associations.AssociationServiceListener;
import org.rioproject.associations.AssociationType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* An implementation of an {@link org.rioproject.associations.Association}.
*
* @author Dennis Reedy
*/
public class DefaultAssociation<T> implements Association<T> {
/**
* The Association state
*/
private State state = State.PENDING;
/**
* The descriptor for this Association
*/
private AssociationDescriptor descriptor;
/**
* LinkedList of associated service instances
*/
private final List<ServiceItem> serviceList = new LinkedList<ServiceItem>();
/**
* Index to associated service list
*/
private final AtomicInteger index = new AtomicInteger(0);
/**
* Collection of AssociationServiceListeners
*/
private final List<AssociationServiceListener<T>> aListeners = new ArrayList<AssociationServiceListener<T>>();
/**
* Executor for futures
*/
private final ExecutorService futuresExecutor = Executors.newCachedThreadPool();
private static final Logger logger = LoggerFactory.getLogger(DefaultAssociation.class);
/**
* Create an Association
*
* @param descriptor The {@link AssociationDescriptor} for
* this association
*/
public DefaultAssociation(AssociationDescriptor descriptor) {
if(descriptor == null)
throw new IllegalArgumentException("descriptor is null");
this.descriptor = descriptor;
}
/**
* Get the AssociationType
*
* @return The AssociationType
*/
public AssociationType getAssociationType() {
return (descriptor.getAssociationType());
}
/**
* Set the Association state
*
* @param state The Association state
*/
public void setState(State state) {
synchronized(this) {
this.state = state;
}
}
/**
* Get the Association state
*
* @return The Association state
*/
public State getState() {
State currentState;
synchronized(this) {
currentState = state;
}
return (currentState);
}
/**
* Get the associated service's name
*
* @return The associated service's name
*/
public String getName() {
return (descriptor.getName());
}
/**
* Get the associated service's OperationalString name
*
* @return The associated service's OperationalString name
*/
public String getOperationalStringName() {
return (descriptor.getOperationalStringName());
}
/**
* Get the AssociationDescriptor
*
* @return The {@link AssociationDescriptor} used to
* create this Association
*/
public AssociationDescriptor getAssociationDescriptor() {
return(descriptor);
}
/**
* Get the number of associated services
*
* @return The number of associated services
*/
public int getServiceCount() {
int count;
synchronized(serviceList) {
count = serviceList.size();
}
if(logger.isTraceEnabled())
logger.trace("returning service count of: {}", count);
return count;
}
/**
* Get the first service Object that can be used to communicate to the
* associated service.
*
* @return The first service (proxy) Object in the collection of associated
* services. If there are no services a null will be returned.
*/
@SuppressWarnings("unchecked")
public T getService() {
T service = null;
ServiceItem item = getServiceItem();
if(item!=null)
service = (T) item.service;
return (service);
}
/**
* Get a future representing pending service association.
*
* @return a Future representing pending service association. A new Future
* is created each time.
*
* @see org.rioproject.associations.AssociationProxy
* @see org.rioproject.associations.ServiceSelectionStrategy
*/
public Future<T> getServiceFuture() {
AssociationFutureTask<T> task = new AssociationFutureTask<T>();
AssociationInjector<T> ai = new AssociationInjector<T>(task);
ai.setTargetPropertyName("proxy");
Collection<T> services = getServices();
if(services.isEmpty()) {
registerAssociationServiceListener(new AssociationFutureListener<T>(ai,
this));
} else {
for(T service : services)
ai.discovered(this, service);
}
return futuresExecutor.submit(task);
}
/**
* Get all Objects that can be used to communicate to all known associated
* services.
*
* @return Array of service Object instances that can be used to
* communicate to all known associated service instances. A new collection
* is allocated each time. If there are no services, an empty collection
* will be returned
*/
@SuppressWarnings("unchecked")
public Collection<T> getServices() {
Collection<T> c = new ArrayList<T>();
ServiceItem[] items = getServiceItems();
for(ServiceItem item : items) {
c.add((T)item.service);
}
return c;
}
/**
* Satisfies the contract of an {@link java.lang.Iterable}, allowing the
* associated services to be the target of a "foreach" statement
*
* @return An {@link java.lang.Iterable} that can be used to iterate over
* the collection of associated services. A new {@code Iterable} is created
* each time.
*/
public Iterator<T> iterator() {
return getServices().iterator();
}
/**
* Get the ServiceItem for the service thats first in the List of services.
*
* @return The ServiceItem for an associated service. If there are no
* services, a null will be returned
*/
public ServiceItem getServiceItem() {
ServiceItem item = null;
synchronized(serviceList) {
if(!serviceList.isEmpty())
item = serviceList.get(0);
}
return (item);
}
/**
* Get the ServiceItem for the associated service. The collection of
* associated services will be searched and if the service proxy is equal
* to a known associated service proxy, the
* {@link net.jini.core.lookup.ServiceID} for that service will be returned
*
* @param service The proxy of an associated service
*
* @return The ServiceItem for an associated service. If the service is
* unknown, a null will be returned
*/
public ServiceItem getServiceItem(T service) {
ServiceItem item = null;
ServiceItem[] items = getServiceItems();
for (ServiceItem item1 : items) {
if (item1.service.equals(service)) {
item = item1;
break;
}
}
return (item);
}
/**
* Get ServiceItem instances for all known associated service instances.
*
* @return Array of ServiceItem instances for all known associated
* service instances. A new array is allocated each time. If there are no
* services, an empty array will be returned
*/
public ServiceItem[] getServiceItems() {
ServiceItem[] items;
synchronized(serviceList) {
items = serviceList.toArray(new ServiceItem[serviceList.size()]);
}
return (items);
}
/**
* Get the next ServiceItem in the collection of associated services.
*
* @return The next ServiceItem in the collection of associated services.
* If there are services, a null will be returned. If the current
* ServiceItem is the last in the collection, the first ServiceItem in the
* collection will be returned
*/
public ServiceItem getNextServiceItem() {
ServiceItem item;
synchronized(serviceList) {
if(serviceList.isEmpty()) {
item = null;
} else {
if(index.get()>=serviceList.size()) {
index.set(0);
}
item = serviceList.get(index.getAndIncrement());
}
}
return item;
}
/**
* Add a service to the Association
*
* @param item The ServiceItem for the service to add
* @return True if added, false if the ServiceItem already exists
*/
@SuppressWarnings("unchecked")
public boolean addServiceItem(ServiceItem item) {
if(item == null)
throw new IllegalArgumentException("item is null");
ServiceItem[] items = getServiceItems();
for (ServiceItem item1 : items) {
if (item1.service.equals(item.service)) {
return false;
}
}
synchronized(serviceList) {
serviceList.add(item);
}
if(logger.isDebugEnabled())
logger.debug("Service count now: {}", serviceList.size());
notifyServiceAdd((T)item.service);
return true;
}
/**
* Remove a service from the Association
*
* @param service The proxy for the Service to remove
* @return The ServiceItem of the removed service
*/
public ServiceItem removeService(T service) {
if(service == null)
throw new IllegalArgumentException("service is null");
ServiceItem item = null;
ServiceItem[] items = getServiceItems();
for (ServiceItem item1 : items) {
if (item1.service.equals(service)) {
synchronized (serviceList) {
if(serviceList.remove(item1))
item = item1;
}
break;
}
}
if(item!=null)
notifyServiceRemoved(service);
return item;
}
/**
* Register an {@link AssociationServiceListener} for notification.
*
* @param al The AssociationServiceListener. If the AssociationServiceListener
* is not null and not already registered it will be added to the collection of
* AssociationServiceListeners
*/
public void registerAssociationServiceListener(AssociationServiceListener<T> al) {
if(al!=null) {
synchronized(aListeners) {
if(!aListeners.contains(al)) {
aListeners.add(al);
}
}
}
}
/**
* Remove a {@link AssociationServiceListener} for notification.
*
* @param al The AssociationServiceListener. If the AssociationServiceListener
* is not null and registered, it will be removed from the collection of
* AssociationServiceListeners
*/
public void removeAssociationServiceListener(AssociationServiceListener<T> al) {
if(al!=null) {
synchronized(aListeners) {
aListeners.remove(al);
}
}
}
public void terminate() {
futuresExecutor.shutdownNow();
}
private void notifyServiceAdd(T service) {
List<AssociationServiceListener<T>> listeners = new ArrayList<AssociationServiceListener<T>>();
synchronized(aListeners) {
listeners.addAll(aListeners);
}
for(AssociationServiceListener<T> a : listeners) {
a.serviceAdded(service);
}
}
private void notifyServiceRemoved(T service) {
List<AssociationServiceListener<T>> listeners = new ArrayList<AssociationServiceListener<T>>();
synchronized(aListeners) {
listeners.addAll(aListeners);
}
for(AssociationServiceListener<T> a : listeners) {
a.serviceRemoved(service);
}
}
class AssociationFutureListener<T> implements AssociationServiceListener<T> {
AssociationInjector<T> ai;
Association<T> a;
AssociationFutureListener(AssociationInjector<T> ai, Association<T> a) {
this.ai = ai;
this.a = a;
}
public void serviceAdded(T service) {
ai.discovered(a, service);
}
/* Left empty, no-op*/
public void serviceRemoved(T service) {
}
}
class AssociationFutureTask<T> implements Callable<T> {
T proxy;
private CountDownLatch counter = new CountDownLatch(1);
public void setProxy(T proxy) {
this.proxy = proxy;
counter.countDown();
}
public T call() throws Exception {
counter.await();
return proxy;
}
}
}