Java tutorial
/****************************************************************************** * Copyright(c) 2005-2007 SoftAMIS (http://www.soft-amis.com) * * All Rights Reserved. * * * * 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 com.zz.cluster4spring.support.provider; import static java.text.MessageFormat.format; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.remoting.RemoteAccessException; import org.springframework.remoting.support.RemoteInvocationFactory; import com.zz.cluster4spring.AutoDiscoveredServiceExporter; import com.zz.cluster4spring.URLException; import com.zz.cluster4spring.support.Endpoint; import com.zz.cluster4spring.support.EndpointFactory; import com.zz.cluster4spring.support.ServiceMoniker; /** * Base abstract class for implementation of <code>EndpointProvider</code> that * automatically discovers services based on service name and service group. * Such auto-discoverable services are published via appropriate * <code>ServicePublisher</code>. Class contains all basic logic related to * discovering, but does not include any references to underlying services * storage. * * @author Andrew Sazonov * @version 1.0 * @param <SI> * type of data used to invoke remote service (such as remote service * URL) * @param <E> * type of endpoints that could be created by this factory */ public abstract class AbstractDiscoveringEndpointProvider<E extends Endpoint<SI>, SI extends ServiceMoniker> extends AbstractUrlListEndpointProvider<E, SI> implements ApplicationListener { private static final Log fLog = LogFactory.getLog(AbstractDiscoveringEndpointProvider.class); /** * Name of service */ protected String fServiceName = null; // cached service key - we don't need to calculate it since this // implementation of provider // always works with one proxy factory protected String fServiceKey = null; /** * Name of protocol */ protected String fProtocolName = null; /** * Group to which service belong */ protected String fServiceGroup = null; protected AbstractDiscoveringEndpointProvider() { super(); } /** * <p> * Determines whether refresh for cached endpoints is required. * Implementation will rely on underlying services registry. * </p> * * * @param aServiceKey * key used to check whether cahe should be refreshed * @return true if endpoints cache should be refreshed. * @see com.zz.cluster4spring.registry.ConsumingRegistry#isDirty(java.io.Serializable) */ protected abstract boolean isRefreshRequiredForCachedEndpoints(String aServiceKey); /** * Returns set of service url's from service locations registry * * @param aServiceKey * key used to obtain service urls * @return found service urls * @throws Exception */ protected abstract Set<SI> obtainServiceUrlsFromRegistry(String aServiceKey) throws Exception; /** * Methods used to notify underlying services registry that particular * service location should be invalidated. * * @param aServiceKey * key used to obtain service urls * @param aServiceInfo * information about particular service location */ protected abstract void invalidateServiceInRegistry(String aServiceKey, SI aServiceInfo); /** * Invoked by Spring as part of bean lifecycle. Adds checking for protocol * name, consuming registry, etc. * * @throws Exception * @see #createDefaultEndpointSelectionPolicy() */ @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); if (getProtocolName() == null) { throw new IllegalArgumentException("Protocol name is not specified for endpoint provider"); } if (fServiceGroup == null) { fServiceGroup = AutoDiscoveredServiceExporter.DEFAULT_SERVICES_GROUP_PREFIX + getProtocolName(); } checkServiceName(); } /** * Obtains endpoints from endpoints cache. First, tries to determine whether * cache should be refreshed. If one should, refreshes cache. After this * returns cached endpoints. * * @param aRemoteInvocationFactory * factory to create remote invocations * @param aEndpointFactory * factory used to create endpoints * @param aBeanName * @return list of endpoints * @see #isRefreshRequiredForCachedEndpoints(String) */ @Override protected List<E> getCachedEndpoints(RemoteInvocationFactory aRemoteInvocationFactory, EndpointFactory<E, SI> aEndpointFactory, String aBeanName) { String serviceKey = obtainServiceKey(aBeanName); List<E> result = null; synchronized (cacheLock) { if (isRefreshRequiredForCachedEndpoints(serviceKey)) { try { refresh(aRemoteInvocationFactory, aEndpointFactory, aBeanName); } catch (RemoteAccessException e) { if (fLog.isErrorEnabled()) { fLog.error("Exception occured during refreshing service endpoints registry", e); } } } result = super.getCachedEndpoints(aRemoteInvocationFactory, aEndpointFactory, aBeanName); } return result; } /** * Creates list of endpoints available for service. Method obtains list of * urls that corresponds to required service key from the * <code>ConsumingRegistry</code> and based on this list tries to create * list of endpoints. * <p/> * If endpoint creation is failed for some service url, method notes such * url and after endpoints creation notifies <code>ConsumingRegistry</code> * that particular service url is invalid. * * @param aRemoteInvocationFactory * factory used to create remote invocation * @param aEndpointFactory * factory used to create endpoints * @param aBeanName * @return list of created endpoints * @throws org.springframework.remoting.RemoteAccessException * throws if list of endpoints could not be created */ @Override protected List<E> doRefreshServiceEndpointsList(RemoteInvocationFactory aRemoteInvocationFactory, EndpointFactory<E, SI> aEndpointFactory, String aBeanName) throws RemoteAccessException { if (fLog.isTraceEnabled()) { String message = format("Starting refreshing of service endpoints list. Bean Name: [{0}]", aBeanName); fLog.trace(message); } // first, list of service "URL's" is obtained Set<SI> serviceURLs = null; String serviceKey = obtainServiceKey(aBeanName); try { serviceURLs = obtainServiceUrlsFromRegistry(serviceKey); } catch (Exception e) { String message = format("Unable to determine list of service URL from registry. Bean Name: [{0}]", aBeanName); throw new RemoteAccessException(message, e); } // For which found URL we trying to create an appropriate Endpoint int size = serviceURLs.size(); List<E> result = new ArrayList<E>(size); List<SI> invalidURLs = new ArrayList<SI>(size); // here we collect URL's // for which creation of // endpoint is failed for (SI serviceURL : serviceURLs) { try { E endpoint = doCreateServiceEndpoint(aRemoteInvocationFactory, aEndpointFactory, aBeanName, serviceURL); if (endpoint != null) { result.add(endpoint); } else { invalidURLs.add(serviceURL); } } catch (URLException e) { invalidURLs.add(serviceURL); fLog.error(serviceURL + " is valid"); fLog.error("??URLExceptionurl?..."); } } if (!invalidURLs.isEmpty()) // we have invalid urls - we should notify // registry about them { if (fLog.isTraceEnabled()) { fLog.trace(format("Invalidating not valid endpoints on refresh. Bean Name: [{0}]", aBeanName)); } for (SI invalidURL : invalidURLs) { markServiceInvalidInternal(serviceKey, serviceURLs, invalidURL); } } if (result.isEmpty()) { String message = format( "Unable to determine at least one server endpoint for service. Bean Name: [{0}]", aBeanName); throw new RemoteAccessException(message); } return result; } /** * Marks given endpoint invalid. This endpoint will not be later used for * methods invocation. Method removes endpoint from cache (if configured to * cache endpoints) and later marks particular service url invalid via * ConsumingRegistry. * * @param aBeanName * name of bean that is used as proxy for remote service * @param aEndpoint * endpoint to be marked invalid * @see #setCacheEndpoints(boolean) */ public void markInvalid(String aBeanName, E aEndpoint) { if (fLog.isTraceEnabled()) { String message = format("Starting endpoint invalidation. Bean Name: [{0}] Endpoint Info: [{1}]", aBeanName, aEndpoint.getServiceInfo()); Throwable e = new Exception(); e.fillInStackTrace(); fLog.trace(message, e); } synchronized (this) { if (cacheEndpoints) { synchronized (cacheLock) { cachedEndpoints.remove(aEndpoint); } } Set<SI> cachedServiceInfos = null; String serviceKey = obtainServiceKey(aBeanName); try { // TMP revisit this - this call may lead to not necessary // discovering of the // TMP service. Probably it's better to add some method like // "hasLocalItems()" // TMP into ConsumingRegistry cachedServiceInfos = obtainServiceUrlsFromRegistry(serviceKey); } catch (Exception e) { if (fLog.isErrorEnabled()) { String message = format( "Unable to obtain list of service urls from registry. Service Name is [{0}] bean name is [{1}]", fServiceName, aBeanName); fLog.error(message, e); } } if (cachedServiceInfos != null) { SI serviceUrl = aEndpoint.getServiceInfo(); markServiceInvalidInternal(serviceKey, cachedServiceInfos, serviceUrl); } } } /** * Handles application event * * @param aEvent * event to handle * @see #onContextRefreshed() * @see #onContextClosed() */ public void onApplicationEvent(ApplicationEvent aEvent) { try { if (aEvent instanceof ContextRefreshedEvent) { onContextRefreshed(); } else if (aEvent instanceof ContextClosedEvent) { onContextClosed(); } } catch (Exception e) { if (fLog.isErrorEnabled()) { String publisherName = getProtocolName(); String message = format("Exception during processing application event. Protocol: [{0}]", publisherName); fLog.error(message, e); } } } /** * Handles <code>ContextRefreshed</code> application event. * * @see #onApplicationEvent(ApplicationEvent) */ protected void onContextRefreshed() { } /** * Handles <code>ContextClosed</code> application event. On closing context * clears endpoints cache * * @see #onApplicationEvent(ApplicationEvent) */ protected void onContextClosed() { if (fLog.isTraceEnabled()) { fLog.trace("Clearing cached endpoints on closing context"); } synchronized (cacheLock) { if (cachedEndpoints != null) { cachedEndpoints.clear(); cachedEndpoints = null; } } } /** * Returns key of service in consuming registry. This key is used to obtain * information about urls for that service. * <p/> * Key is composed in form <code>serviceGroup/serviceName</code>. If service * name is not specified during provider configuration, given name of proxy * bean is used instead of service name. * * @param aBeanName * name of proxy bean * @return service key used to obtain services from * <code>ConsumingRegistry</code> */ protected String obtainServiceKey(String aBeanName) { String result = null; if (fServiceKey == null) // calculate service key only once - and later // use cached value { result = createServiceKey(aBeanName); } else { result = fServiceKey; } return result; } /** * Represensts implementation of actual algorythm of service key * calculation. Currntly, it simply creates key in the following form: * <code>serviceGroup/serviceName</code> This method should be overriden if * some custom implementation is necesary. * * @param aBeanName * @return key for service */ protected String createServiceKey(String aBeanName) { String result; String serviceName = null; if (fServiceName == null) { serviceName = aBeanName; } else { serviceName = fServiceName; } StringBuilder tmp = new StringBuilder(100); tmp.append(fServiceGroup); tmp.append("/"); tmp.append(serviceName); result = tmp.toString(); return result; } /** * Notifies underlying registry that given service url under given service * key is invalid if service url is contained in provided set of service * urls * * @param aServiceKey * key used to identify services url in ConsumingRegistry * @param aServiceInfos * list of services urlsurl * @param aServiceInfo * service url that should be marked invalidurl */ protected void markServiceInvalidInternal(String aServiceKey, Set<SI> aServiceInfos, SI aServiceInfo) { if (fLog.isTraceEnabled()) { String message = format( "Starting processing service invalidation request. Service Key: [{0}] Service Info: [{1}]", aServiceKey, aServiceInfo); Throwable ex = new Exception(); // just to have more informative // stacktrace in log ex.fillInStackTrace(); fLog.trace(message, ex); } if (aServiceInfos.remove(aServiceInfo)) { if (fLog.isTraceEnabled()) { fLog.trace("Item is forwareded for registry to invalidation"); } // if we were able to remove service, we need to notify registry invalidateServiceInRegistry(aServiceKey, aServiceInfo); } else { if (fLog.isTraceEnabled()) { fLog.trace(format("Service invalidation was silently skipped. Service Info: [{0}]", aServiceInfo)); } } } protected void checkServiceName() { if (fServiceName == null) { if (DiscoveringEndpointProvider.fLog.isWarnEnabled()) { DiscoveringEndpointProvider.fLog .warn("Service name is not specified. Bean name will be used instead"); } } else { fServiceKey = obtainServiceKey(null); } } /** * Returns group to which service belong * * @return group to which service belong */ public String getServiceGroup() { return fServiceGroup; } /** * Sets group to which service belong. If group name is not specified * explicitely, it will be created by default. Name of group on client * should correspond to name of group on server. * * @param aServiceGroup * group to which service belong */ public void setServiceGroup(String aServiceGroup) { fServiceGroup = aServiceGroup; } /** * Returns name of service * * @return name of service */ public String getServiceName() { return fServiceName; } /** * Sets name of service * * @param aServiceName * name of service */ public void setServiceName(String aServiceName) { fServiceName = aServiceName; } /** * Returns name of protocol * * @return name of protocol */ public String getProtocolName() { return fProtocolName; } /** * Sets name of protocol. Used for logging and to form default name of group * if service group is not specified explicitely * * @param aProtocolName * name of protocol */ public void setProtocolName(String aProtocolName) { fProtocolName = aProtocolName; } }