/*
* #%L
* Service Locator Client for CXF
* %%
* Copyright (C) 2011 - 2012 Talend 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.
* #L%
*/
package org.talend.esb.servicelocator.client.internal;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.apache.zookeeper.ZooKeeper;
import org.talend.esb.servicelocator.client.*;
import org.talend.esb.servicelocator.client.internal.zk.ZKBackend;
/**
* This is the entry point for clients of the Service Locator. To access the
* Service Locator clients have to first {@link #connect() connect} to the
* Service Locator to get a session assigned. Once the connection is established
* the client will periodically send heart beats to the server to keep the
* session alive.
* <p/>
* The Service Locator provides the following operations.
* <ul>
* <li>An endpoint for a specific service can be registered.
* <li>All endpoints for a specific service that were registered before by other
* clients can be looked up.
* </ul>
*/
public class ServiceLocatorImpl implements ServiceLocator, ExpiredEndpointCollector {
private static final Logger LOG = Logger.getLogger(ServiceLocatorImpl.class.getName());
private ServiceLocatorBackend backend;
private EndpointTransformer transformer = new EndpointTransformerImpl();
private Boolean endpointCollectionEnable;
private Integer endpointCollectionInterval;
private Timer timer;
private int schedulerRequestCounter = 0;
/**
* {@inheritDoc}
*/
@Override
public synchronized void connect() throws InterruptedException, ServiceLocatorException {
getBackend().connect();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void disconnect() throws InterruptedException, ServiceLocatorException {
getBackend().disconnect();
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void register(QName serviceName, String endpoint) throws ServiceLocatorException,
InterruptedException {
register(new SimpleEndpoint(serviceName, endpoint), false);
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void register(QName serviceName, String endpoint, boolean persistent)
throws ServiceLocatorException, InterruptedException {
register(new SimpleEndpoint(serviceName, endpoint), persistent);
}
/**
* {@inheritDoc}
*/
@Override
public void register(QName serviceName, String endpoint, SLProperties properties)
throws ServiceLocatorException, InterruptedException {
register(new SimpleEndpoint(serviceName, endpoint, properties), false);
}
/**
* {@inheritDoc}
*/
@Override
public void register(QName serviceName, String endpoint, SLProperties properties, boolean persistent)
throws ServiceLocatorException, InterruptedException {
register(new SimpleEndpoint(serviceName, endpoint, properties), persistent);
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void register(Endpoint epProvider) throws ServiceLocatorException,
InterruptedException {
register(epProvider, false);
}
@Override
public synchronized void register(Endpoint epProvider, boolean persistent)
throws ServiceLocatorException, InterruptedException {
QName serviceName = epProvider.getServiceName();
String endpoint = epProvider.getAddress();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Registering endpoint " + endpoint + " for service " + serviceName + "...");
}
long lastTimeStarted = System.currentTimeMillis();
long lastTimeStopped = -1;
RootNode rootNode = getBackend().connect();
ServiceNode serviceNode = rootNode.getServiceNode(serviceName);
serviceNode.ensureExists();
EndpointNode endpointNode = serviceNode.getEndPoint(endpoint);
if (endpointNode.exists()) {
byte[] content = endpointNode.getContent();
SLEndpoint oldEndpoint = transformer.toSLEndpoint(serviceName, content, false);
lastTimeStopped = oldEndpoint.getLastTimeStopped();
}
byte[] content = createContent(epProvider, lastTimeStarted, lastTimeStopped);
endpointNode.ensureExists(content);
endpointNode.setLive(persistent);
}
@Override
public synchronized void unregister(Endpoint epProvider) throws ServiceLocatorException,
InterruptedException {
QName serviceName = epProvider.getServiceName();
String endpoint = epProvider.getAddress();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Unregistering endpoint " + endpoint + " for service " + serviceName + "...");
}
long lastTimeStarted = -1;
long lastTimeStopped = System.currentTimeMillis();
RootNode rootNode = getBackend().connect();
ServiceNode serviceNode = rootNode.getServiceNode(serviceName);
EndpointNode endpointNode = serviceNode.getEndPoint(endpoint);
if (endpointNode.exists()) {
byte[] oldContent = endpointNode.getContent();
SLEndpoint oldEndpoint = transformer.toSLEndpoint(serviceName, oldContent, false);
lastTimeStarted = oldEndpoint.getLastTimeStarted();
endpointNode.setOffline();
byte[] content = createContent(epProvider, lastTimeStarted, lastTimeStopped);
endpointNode.setContent(content);
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void unregister(QName serviceName, String endpoint) throws ServiceLocatorException,
InterruptedException {
unregister(new SimpleEndpoint(serviceName, endpoint, null));
}
@Override
public void updateTimetolive(QName serviceName, String endpoint, int timetolive)
throws ServiceLocatorException, InterruptedException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Updating expiring time to happen in " + timetolive + " seconds on endpoint " + endpoint
+ " for service " + serviceName + "...");
}
if (timetolive < 0) {
throw new WrongArgumentException("Time-to-live cannot be negative.");
}
if (timetolive == 0) {
throw new WrongArgumentException("Time-to-live cannot be zero.");
}
RootNode rootNode = getBackend().connect();
ServiceNode serviceNode = rootNode.getServiceNode(serviceName);
EndpointNode endpointNode = serviceNode.getEndPoint(endpoint);
if (endpointNode.exists()) {
endpointNode.setLive(true);
endpointNode.setExpiryTime(new Date(System.currentTimeMillis() + timetolive * 1000), true);
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Unable to update endpoint expiring time for endpoint " + endpoint + " for service "
+ serviceName + " because it does not exist.");
}
throw new EndpointNotFoundException("Endpoint " + endpoint + " for service " + serviceName
+ " does not exist.");
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void removeEndpoint(QName serviceName, String endpoint)
throws ServiceLocatorException, InterruptedException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Removing endpoint " + endpoint + " for service " + serviceName + "...");
}
RootNode rootNode = getBackend().connect();
ServiceNode serviceNode = rootNode.getServiceNode(serviceName);
EndpointNode endpointNode = serviceNode.getEndPoint(endpoint);
endpointNode.ensureRemoved();
}
/**
* {@inheritDoc}
*/
@Override
public List<QName> getServices() throws InterruptedException, ServiceLocatorException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Getting all services...");
}
RootNode rootNode = getBackend().connect();
return rootNode.getServiceNames();
}
/**
* {@inheritDoc}
*/
@Override
synchronized public List<SLEndpoint> getEndpoints(final QName serviceName)
throws ServiceLocatorException, InterruptedException {
RootNode rootNode = getBackend().connect();
ServiceNode serviceNode = rootNode.getServiceNode(serviceName);
if (serviceNode.exists()) {
List<EndpointNode> endpointNodes = serviceNode.getEndPoints();
List<SLEndpoint> slEndpoints = new ArrayList<SLEndpoint>(endpointNodes.size());
for (EndpointNode endpointNode : endpointNodes) {
byte[] content = endpointNode.getContent();
final boolean isLive = endpointNode.isLive();
SLEndpoint slEndpoint = transformer.toSLEndpoint(serviceName, content, isLive);
slEndpoints.add(slEndpoint);
}
return slEndpoints;
} else {
return Collections.emptyList();
}
}
/**
* {@inheritDoc}
*/
@Override
public SLEndpoint getEndpoint(final QName serviceName, final String endpoint)
throws ServiceLocatorException, InterruptedException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Get endpoint information for endpoint " + endpoint + " within service " + serviceName
+ "...");
}
RootNode rootNode = getBackend().connect();
ServiceNode serviceNode = rootNode.getServiceNode(serviceName);
EndpointNode endpointNode = serviceNode.getEndPoint(endpoint);
if (endpointNode.exists()) {
byte[] content = endpointNode.getContent();
final boolean isLive = endpointNode.isLive();
return transformer.toSLEndpoint(serviceName, content, isLive);
} else {
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public synchronized List<String> getEndpointNames(QName serviceName) throws ServiceLocatorException,
InterruptedException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Get all endpoint names of service " + serviceName + "...");
}
List<String> children;
RootNode rootNode = getBackend().connect();
ServiceNode serviceNode = rootNode.getServiceNode(serviceName);
if (serviceNode.exists()) {
children = serviceNode.getEndpointNames();
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Lookup of service " + serviceName + " failed, service is not known.");
}
children = Collections.emptyList();
}
return children;
}
/**
* {@inheritDoc}
*/
@Override
public List<String> lookup(QName serviceName) throws ServiceLocatorException, InterruptedException {
return lookup(serviceName, SLPropertiesMatcher.ALL_MATCHER);
}
/**
* {@inheritDoc}
*/
@Override
public synchronized List<String> lookup(QName serviceName, SLPropertiesMatcher matcher)
throws ServiceLocatorException, InterruptedException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Looking up endpoints of service " + serviceName + "...");
}
List<String> liveEndpoints;
RootNode rootNode = getBackend().connect();
ServiceNode serviceNode = rootNode.getServiceNode(serviceName);
if (serviceNode.exists()) {
liveEndpoints = new ArrayList<String>();
List<EndpointNode> endpointNodes = serviceNode.getEndPoints();
for (EndpointNode endpointNode : endpointNodes) {
if (endpointNode.isLive()) {
byte[] content = endpointNode.getContent();
SLEndpoint endpoint = transformer.toSLEndpoint(serviceName, content, true);
SLProperties props = endpoint.getProperties();
if (LOG.isLoggable(Level.FINE)) {
StringBuilder sb = new StringBuilder();
for (String prop : props.getPropertyNames()) {
sb.append(prop + " : ");
for (String value : props.getValues(prop)) {
sb.append(value + " ");
}
sb.append("\n");
}
LOG.fine("Lookup of service " + serviceName + " props = " + sb.toString());
LOG.fine("matcher = " + matcher.toString());
}
if (matcher.isMatching(props)) {
liveEndpoints.add(endpointNode.getEndpointName());
if (LOG.isLoggable(Level.FINE))
LOG.fine("matched = " + endpointNode.getEndpointName());
} else if (LOG.isLoggable(Level.FINE))
LOG.fine("not matched = " + endpointNode.getEndpointName());
}
}
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Lookup of service " + serviceName + " failed, service is not known.");
}
liveEndpoints = Collections.emptyList();
}
return liveEndpoints;
}
/**
* Specify the endpoints of all the instances belonging to the service
* locator ensemble this object might potentially be talking to when
* {@link #connect() connecting}. The object will one by one pick an
* endpoint (the order is non-deterministic) to connect to the service
* locator until a connection is established.
*
* @param endpoints comma separated list of endpoints,each corresponding to a
* service locator instance. Each endpoint is specified as a
* host:port pair. At least one endpoint must be specified. Valid
* exmaples are: "127.0.0.1:2181" or
* "sl1.example.com:3210, sl2.example.com:3210, sl3.example.com:3210"
*/
public void setLocatorEndpoints(String endpoints) {
((ZKBackend) getBackend()).setLocatorEndpoints(endpoints);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Locator endpoints set to " + endpoints);
}
}
/**
* Specify the time out of the session established at the server. The
* session is kept alive by requests sent by this client object. If the
* session is idle for a period of time that would timeout the session, the
* client will send a PING request to keep the session alive.
*
* @param timeout timeout in milliseconds, must be greater than zero and less
* than 60000.
*/
public void setSessionTimeout(int timeout) {
((ZKBackend) getBackend()).setSessionTimeout(timeout);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Locator session timeout set to: " + timeout);
}
}
/**
* Specify the time this client waits {@link #connect() for a connection to
* get established}.
*
* @param timeout timeout in milliseconds, must be greater than zero
*/
public void setConnectionTimeout(int timeout) {
((ZKBackend) getBackend()).setConnectionTimeout(timeout);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Locator connection timeout set to: " + timeout);
}
}
public void setName(String name) {
((ZKBackend) getBackend()).setUserName(name);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("User name set to: " + name);
}
}
public void setPassword(String passWord) {
((ZKBackend) getBackend()).setPassword(passWord);
}
public void setEndpointTransformer(EndpointTransformer endpointTransformer) {
transformer = endpointTransformer;
}
public void setEndpointCollectionEnable(Boolean endpointCollectionDisable) {
this.endpointCollectionEnable = endpointCollectionDisable;
}
public void setEndpointCollectionInterval(Integer endpointCollectionInterval) {
this.endpointCollectionInterval = endpointCollectionInterval;
}
/**
* {@inheritDoc}
*/
@Override
public void addPostConnectAction(PostConnectAction postConnectAction) {
backend.addPostConnectAction(postConnectAction);
}
/**
* {@inheritDoc}
*/
@Override
public void removePostConnectAction(PostConnectAction postConnectAction) {
backend.removePostConnectAction(postConnectAction);
}
private byte[] createContent(Endpoint eprProvider, long lastTimeStarted, long lastTimeStopped)
throws ServiceLocatorException {
return transformer.fromEndpoint(eprProvider, lastTimeStarted, lastTimeStopped);
}
private ServiceLocatorBackend getBackend() {
if (backend == null) {
backend = new ZKBackend();
}
return backend;
}
public void setBackend(ServiceLocatorBackend backend) {
this.backend = backend;
}
protected ZooKeeper createZooKeeper(CountDownLatch connectionLatch) throws ServiceLocatorException {
/*
* try { return new ZooKeeper(locatorEndpoints, sessionTimeout, new
* WatcherImpl(connectionLatch)); } catch (IOException e) { throw new
* ServiceLocatorException("At least one of the endpoints " +
* locatorEndpoints + " does not represent a valid address."); }
*/
return null;
}
@Override
public synchronized void startScheduledCollection() {
if (endpointCollectionEnable != null && !endpointCollectionEnable) {
LOG.info("Expired endpoint collection is disabled in configuration.");
return;
}
if (endpointCollectionInterval == null) {
LOG.severe("Expired endpoint collection interval is not set.");
return;
}
Long collectionInterval = endpointCollectionInterval * 1000L;
if (collectionInterval < 5000L) {
LOG.severe("Expired endpoint collection interval has invalid value '" + collectionInterval
+ "'. " + "It should be >= 5000.");
return;
}
schedulerRequestCounter++;
if (timer != null) {
return;
}
if (schedulerRequestCounter != 1) {
LOG.warning("Expired endpoint collector schedule is inconsistent.");
}
timer = new Timer("Expired-Endpoint-Collector-Timer", true);
timer.schedule(new TimerTask() {
@Override
public void run() {
performCollection();
}
}, collectionInterval, collectionInterval);
}
@Override
public synchronized void stopScheduledCollection() {
if (timer == null) {
return;
}
schedulerRequestCounter--;
if (schedulerRequestCounter <= 0) {
timer.cancel();
timer = null;
schedulerRequestCounter = 0;
}
}
@Override
public synchronized void performCollection() {
LOG.fine("Performing expired endpoint collection.");
Date now = new Date();
try {
RootNode root = getBackend().connect();
List<QName> svcs = root.getServiceNames();
for (QName svc : svcs) {
ServiceNode svcNode = root.getServiceNode(svc);
List<EndpointNode> epts = svcNode.getEndPoints();
for (EndpointNode ept : epts) {
Date expTime = ept.getExpiryTime();
if (expTime != null && expTime.before(now)) {
unregisterEndpoint(svc, ept.getEndpointName());
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void unregisterEndpoint(QName serviceName, String endpointName) {
try {
unregister(serviceName, endpointName);
} catch (Exception e) {
if (e instanceof ServiceLocatorException || e instanceof InterruptedException) {
LOG.warning("Exception during unregistering expired endpoint: " + e);
} else {
throw new RuntimeException("Unexpected exception during unregistering expired endpoint.", e);
}
}
}
}