blob: 87bf3dce64d5dbecb28cab650c360a9047e20c9e [file] [log] [blame]
/*
* Copyright 2013 The Apache Software Foundation.
*
* 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.apache.juddi.v3.client.subscription;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.rmi.UnexpectedException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.Endpoint;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.juddi.v3.client.config.UDDIClerk;
import org.apache.juddi.v3.client.config.UDDIClient;
import org.apache.juddi.v3.client.cryptor.DigSigUtil;
import org.apache.juddi.v3.client.transport.Transport;
import org.apache.juddi.v3.client.transport.TransportException;
import org.uddi.api_v3.AccessPoint;
import org.uddi.api_v3.BindingDetail;
import org.uddi.api_v3.BindingTemplate;
import org.uddi.api_v3.BusinessDetail;
import org.uddi.api_v3.DeleteBinding;
import org.uddi.api_v3.DispositionReport;
import org.uddi.api_v3.GetBindingDetail;
import org.uddi.api_v3.GetBusinessDetail;
import org.uddi.api_v3.GetServiceDetail;
import org.uddi.api_v3.Result;
import org.uddi.api_v3.SaveBinding;
import org.uddi.api_v3.ServiceDetail;
import org.uddi.api_v3.TModelInstanceDetails;
import org.uddi.api_v3.TModelInstanceInfo;
import org.uddi.subr_v3.NotifySubscriptionListener;
import org.uddi.v3_service.DispositionReportFaultMessage;
import org.uddi.v3_service.UDDIInquiryPortType;
import org.uddi.v3_service.UDDIPublicationPortType;
/**
* WebService which implements the UDDI v3 SubscriptionListener API. This
* service will be called by the UDDI registry when any change to a Service or
* BindingTemplate call in to it.
* <h1>Usage scenario</h1>
* Use this call for when you need to be notified from a UDDI server that either
* a UDDI entity was created, changed, or deleted via the UDDI Subscription web
* service. This class will start up an embedded Jetty server (built into the
* JRE). You can then register your code to be notified of any inbound messages
* received from the UDDI server asynchronously. Here's some sample code.
* <pre>
* UDDIClient c = new UDDIClient("META-INF/uddiclient.xml");
* UDDIClerk clerk = c.getClerk("default");
* TModel createKeyGenator = UDDIClerk.createKeyGenator("uddi:org.apache.juddi:test:keygenerator", "Test domain", "en");
* clerk.register(createKeyGenator);
* BindingTemplate start = SubscriptionCallbackListener.start(c, "default");
* //keep alive
* while(running)
* Thread.sleep(1000);
* SubscriptionCallbackListener.stop(c, "default", start.getBindingKey());
* </pre>
*
* @author <a href="mailto:alexoree@apache.org">Alex O'Ree</a>
* @since 3.2
*/
@WebService(serviceName = "UDDISubscriptionListenerClientService",
endpointInterface = "org.uddi.v3_service.UDDISubscriptionListenerPortType",
targetNamespace = "urn:uddi-org:v3_service")
public class SubscriptionCallbackListener implements org.uddi.v3_service.UDDISubscriptionListenerPortType, Runnable {
/**
* adds a shutdown hook to trap and warn about leaving the server
* running on exit
*/
public SubscriptionCallbackListener() {
Runtime runtime = Runtime.getRuntime();
runtime.addShutdownHook(new Thread(this));
}
/**
* used for unit tests, may return null if the endpoint isn't started
* yet
*
* @return gets an instance
*/
protected static SubscriptionCallbackListener getInstance() {
return instance;
}
private static final Log log = LogFactory.getLog(SubscriptionCallbackListener.class);
private static List<ISubscriptionCallback> callbacks = new ArrayList<ISubscriptionCallback>();
private static SubscriptionCallbackListener instance = null;
private static Endpoint ep = null;
/**
* Starts a embedded Jetty web server (comes with the JDK) using the
* Endpoint API.
*
* @param client
* @param cfg_node_name
* @param endpoint this is the url that a UDDI server would use to
* connect to the client's subscription listener service Recommend
* specifying a port that is firewall friendly
* @param keydomain
*
* @param autoregister
* @param behavior
* @param serviceKey
* @return null, if and only if callbackBusinessService was null,
* otherwise the modified callbackBusinessService is returned. Clients
* can then use it to continue the registration process.
* @throws ServiceAlreadyStartedException
* @throws SecurityException
* @throws ConfigurationException
* @throws TransportException
* @throws DispositionReportFaultMessage
* @throws java.rmi.UnexpectedException
* @throws
* org.apache.juddi.v3.client.subscription.RegistrationAbortedException
* @throws java.net.MalformedURLException
* @throws org.apache.juddi.v3.client.subscription.UnableToSignException
* @see Endpoint
*/
public static synchronized BindingTemplate start(UDDIClient client, String cfg_node_name, String endpoint,
String keydomain, boolean autoregister, String serviceKey,
SignatureBehavior behavior) throws ServiceAlreadyStartedException, SecurityException, ConfigurationException, TransportException, DispositionReportFaultMessage, RemoteException, UnexpectedException, RegistrationAbortedException, UnableToSignException, MalformedURLException {
if (instance == null) {
instance = new SubscriptionCallbackListener();
}
if (ep != null && ep.isPublished()) {
throw new ServiceAlreadyStartedException();
}
URL url = null;
try {
url = new URL(endpoint);
} catch (Exception ex) {
log.warn("Callback endpoint couldn't be parsed, generating a random one: " + ex.getMessage());
url = new URL("http://" + GetHostname() + ":" + GetRandomPort(4000) + "/" + UUID.randomUUID().toString());
}
endpoint = url.toString();
//if (endpoint == null || endpoint.equals("")) {
// endpoint = "http://" + GetHostname() + ":" + GetRandomPort(url.getPort()) + "/" + UUID.randomUUID().toString();
int attempts = 5;
if (ep == null) {
while ((ep == null || !ep.isPublished()) && attempts > 0) {
try {
ep = Endpoint.publish(endpoint, instance);
callback = endpoint;
} catch (Exception be) {
log.info("trouble starting callback at " + endpoint + ", trying again with a random port: " + be.getMessage());
log.debug(be);
attempts--;
//if (be instanceof java.net.BindException) {
url = new URL("http://" + url.getHost() + ":" + GetRandomPort(url.getPort()) + "/" + url.getPath());
endpoint = url.toString();
}
}
}
if (ep == null || !ep.isPublished()) {
log.warn("Unable to start callback endpoint, aborting");
throw new SecurityException("unable to start endpoint, view previous errors for reason");
}
log.info("Endpoint started at " + callback);
BindingTemplate bt = new BindingTemplate();
bt.setAccessPoint(new AccessPoint());
bt.getAccessPoint().setValue(callback);
bt.getAccessPoint().setUseType("endPoint");
TModelInstanceInfo instanceInfo = new TModelInstanceInfo();
instanceInfo.setTModelKey("uddi:uddi.org:transport:http");
bt.setTModelInstanceDetails(new TModelInstanceDetails());
bt.getTModelInstanceDetails().getTModelInstanceInfo().add(instanceInfo);
bt.setServiceKey(serviceKey);
if (keydomain.endsWith(":")) {
bt.setBindingKey(keydomain + GetHostname() + "_subscription_callback");
} else {
bt.setBindingKey(keydomain + ":" + GetHostname() + "_subscription_callback");
}
if (autoregister) {
bt = registerBinding(client, cfg_node_name, bt, behavior);
}
return bt;
}
/**
* Starts a subscription callback service using the juddi client config
* file's settings. This will use the config setting PROPERTY_NODE, or
* default if not defined
*
* @param client
* @return a bindingtemplate populated with the relevant information for
* most UDDI servers for asynchronous callbacks.
* @throws ServiceAlreadyStartedException
* @throws SecurityException
* @throws ConfigurationException
* @throws TransportException
* @throws DispositionReportFaultMessage
* @throws UnexpectedException
* @throws RemoteException
* @throws
* org.apache.juddi.v3.client.subscription.RegistrationAbortedException
* @throws org.apache.juddi.v3.client.subscription.UnableToSignException
* @throws java.net.MalformedURLException
*/
public static synchronized BindingTemplate start(UDDIClient client) throws ServiceAlreadyStartedException, SecurityException, ConfigurationException, TransportException, DispositionReportFaultMessage, UnexpectedException, RemoteException, RegistrationAbortedException, UnableToSignException, MalformedURLException {
return start(client, client.getClientConfig().getConfiguration().getString(PROPERTY_NODE, "default"));
}
/**
* Starts a subscription callback service using the juddi client config
* file's settings. This will use the specified node
*
* @param client
* @param cfg_node_name the node to connect to and perform all
* operations on
* @return a bindingtemplate populated with the relevant information for
* most UDDI servers for asynchronous callbacks.
* @throws ServiceAlreadyStartedException
* @throws SecurityException
* @throws ConfigurationException
* @throws TransportException
* @throws DispositionReportFaultMessage
* @throws UnexpectedException
* @throws RemoteException
* @throws
* org.apache.juddi.v3.client.subscription.RegistrationAbortedException
* @throws org.apache.juddi.v3.client.subscription.UnableToSignException
* @throws java.net.MalformedURLException
*/
public static synchronized BindingTemplate start(UDDIClient client, String cfg_node_name) throws ServiceAlreadyStartedException, SecurityException, ConfigurationException, TransportException, DispositionReportFaultMessage, UnexpectedException, RemoteException, RegistrationAbortedException, UnableToSignException, MalformedURLException {
try {
boolean reg = (client.getClientConfig().getConfiguration().getBoolean(PROPERTY_AUTOREG_BT, false));
String endpoint = client.getClientConfig().getConfiguration().getString(PROPERTY_LISTENURL);
String kd = client.getClientConfig().getConfiguration().getString(PROPERTY_KEYDOMAIN);
String key = client.getClientConfig().getConfiguration().getString(PROPERTY_AUTOREG_SERVICE_KEY);
String sbs = client.getClientConfig().getConfiguration().getString(PROPERTY_SIGNATURE_BEHAVIOR);
SignatureBehavior sb = SignatureBehavior.DoNothing;
try {
sb = SignatureBehavior.valueOf(sbs);
} catch (Exception ex) {
log.warn("Unable to parse config setting for SignatureBehavior, defaulting to DoNothing", ex);
}
return start(client, cfg_node_name, endpoint, kd, reg, key, sb);
} catch (ConfigurationException ex) {
throw new ConfigurationException("failed to some critical settings from the juddi client config file. I won't be able to fire up the subscription callback endpoint ", ex);
}
}
private static String callback = null;
/**
* gets the current callback url, may be null if the endpoint isn't
* started yet
*
* @return the current callback url or null
*/
public static String getCallbackURL() {
return callback;
}
/**
* Registers an implementation of ISubscriptionCallback for subscription
* callbacks from a UDDI server.
*
*
* @param callback if null, no action is taken
*/
public static synchronized void registerCallback(ISubscriptionCallback callback) {
if (callback != null) {
if (!callbacks.contains(callback)) {
callbacks.add(callback);
}
}
}
/**
* unregisters a ISubscriptionCallback for callbacks
*
* @param callback if null, no action is taken
*/
public static synchronized void unRegisterCallback(ISubscriptionCallback callback) {
if (callback != null) {
if (callbacks.contains(callback)) {
callbacks.remove(callback);
}
}
}
/**
* config parameter
*/
public static final String PROPERTY_LISTENURL = "client.subscriptionCallbacks.listenUrl";
/**
* config parameter, if not defined, default will be used
*/
public static final String PROPERTY_NODE = "client.subscriptionCallbacks.node";
/**
* config parameter
*/
public static final String PROPERTY_KEYDOMAIN = "client.subscriptionCallbacks.keyDomain";
/**
* config parameter true/false
*/
public static final String PROPERTY_AUTOREG_BT = "client.subscriptionCallbacks.autoRegisterBindingTemplate";
/**
* config parameter business key
*/
public static final String PROPERTY_AUTOREG_SERVICE_KEY = "client.subscriptionCallbacks.autoRegisterBusinessServiceKey";
/**
* config parameter
*
* @see SignatureBehavior
*/
public static final String PROPERTY_SIGNATURE_BEHAVIOR = "client.subscriptionCallbacks.signatureBehavior";
/**
* return true if and only if the binding exists and is signed
*
* @param bindingKey
* @param uddiInquiryService
* @param token
* @param behavior
* @return true/false
*/
private static boolean CheckExistingBindingForSignature(String bindingKey, UDDIInquiryPortType uddiInquiryService, String token, SignatureBehavior behavior) {
GetBindingDetail gbd = new GetBindingDetail();
gbd.setAuthInfo(token);
gbd.getBindingKey().add(bindingKey);
try {
BindingDetail bindingDetail = uddiInquiryService.getBindingDetail(gbd);
if (bindingDetail != null
&& !bindingDetail.getBindingTemplate().isEmpty()
&& !bindingDetail.getBindingTemplate().get(0).getSignature().isEmpty()) {
log.info("the binding template with key=" + bindingKey + " exists and is digitally signed");
}
return true;
} catch (Exception ex) {
log.debug("Error caught checking for the existence of and if a signature is present for binding key " + bindingKey + " this may be ignorable", ex);
}
return false;
}
private static boolean CheckServiceAndParentForSignature(String serviceKey, UDDIInquiryPortType uddiInquiryService, String token) throws UnexpectedResponseException {
GetServiceDetail gsd = new GetServiceDetail();
gsd.setAuthInfo(token);
gsd.getServiceKey().add(serviceKey);
String bizkey = null;
try {
ServiceDetail serviceDetail = uddiInquiryService.getServiceDetail(gsd);
if (serviceDetail != null) {
if (!serviceDetail.getBusinessService().isEmpty()) {
bizkey = serviceDetail.getBusinessService().get(0).getBusinessKey();
if (!serviceDetail.getBusinessService().get(0).getSignature().isEmpty()) {
log.info("the service with key=" + serviceKey + " exists and is digitally signed");
return true;
}
}
}
} catch (Exception ex) {
log.info("Error caught checking for the existence of and if a signature is present for service key " + serviceKey, ex);
throw new UnexpectedResponseException("Error caught checking for the existence of and if a signature is present for service key " + serviceKey, ex);
}
if (bizkey == null) {
throw new UnexpectedResponseException("The service with key " + serviceKey + " parent's business key could not be determined. This is unexpected");
}
GetBusinessDetail gbd = new GetBusinessDetail();
gbd.setAuthInfo(token);
gbd.getBusinessKey().add(bizkey);
try {
BusinessDetail businessDetail = uddiInquiryService.getBusinessDetail(gbd);
if (businessDetail != null && !businessDetail.getBusinessEntity().isEmpty()) {
if (!businessDetail.getBusinessEntity().get(0).getSignature().isEmpty()) {
log.info("the business with key=" + bizkey + " exists and is digitally signed");
return true;
}
}
} catch (Exception ex) {
log.info("Error caught checking for the existence of and if a signature is present for business key " + bizkey, ex);
throw new UnexpectedResponseException("Error caught checking for the existence of and if a signature is present for business key " + bizkey, ex);
}
return false;
}
private static int GetRandomPort(int oldport) {
if (oldport <= 0) {
oldport = 4000;
}
return oldport + new Random().nextInt(99);
}
/**
* shutdown hook
*/
@Override
public void run() {
shutdown();
}
private synchronized void shutdown() {
if (ep != null && !ep.isPublished()) {
log.fatal("Hey, someone should tell the developer to call SubscriptionCallbackListern.stop(...) before ending the program. Stopping endpoint at " + callback);
unregisterAllCallbacks();
ep.stop();
ep = null;
callback = null;
}
}
/**
* This defines how the automatic subscription binding template is
* suppose to behave
*/
public enum SignatureBehavior {
/**
* Aborts the save request if either the entity exists and is
* already signed, or if any parent uddi element is signed
*/
AbortIfSigned,
/**
* Signs this element. Warning: It may cause signatures of
* parent elements to become invalid. If unable to sign, an
* exception will be thrown
*/
SignAlways,
/**
* Signs this element, but only if parents are not signed. If
* unable to sign, an exception will be thrown
*/
SignOnlyIfParentIsntSigned,
/**
* Do nothing, don't sign it and don't check if a parent item is
* signed or not.
*/
DoNothing
}
/**
* Registers a UDDI binding template that represents the subscription
* callback endpoint
*
* @param client
* @param cfg_node_name
* @param bt - Binding Template
* @param behavior
* @return a binding template
* @throws ServiceAlreadyStartedException
* @throws SecurityException
* @throws ConfigurationException
* @throws TransportException
* @throws DispositionReportFaultMessage
* @throws RemoteException
* @throws UnexpectedException
* @throws RegistrationAbortedException
* @throws UnableToSignException
*/
public static BindingTemplate registerBinding(UDDIClient client, String cfg_node_name, BindingTemplate bt, SignatureBehavior behavior) throws ServiceAlreadyStartedException, SecurityException, ConfigurationException, TransportException, DispositionReportFaultMessage, RemoteException, UnexpectedException, RegistrationAbortedException, UnableToSignException {
UDDIClerk clerk = client.getClerk(cfg_node_name);
Transport tp = client.getTransport(cfg_node_name);
UDDIInquiryPortType uddiInquiryService = tp.getUDDIInquiryService();
UDDIPublicationPortType uddiPublishService = tp.getUDDIPublishService();
String token = clerk.getAuthToken(clerk.getUDDINode().getSecurityUrl());
switch (behavior) {
case AbortIfSigned:
if (CheckExistingBindingForSignature(bt.getBindingKey(), uddiInquiryService, token, behavior)) {
throw new RegistrationAbortedException("Aborting, Either the item exists and is signed");
}
if (CheckServiceAndParentForSignature(bt.getServiceKey(), uddiInquiryService, token)) {
throw new RegistrationAbortedException("Aborting, Either the service or busness is signed");
}
break;
case DoNothing:
break;
case SignAlways:
try {
DigSigUtil ds = new DigSigUtil(client.getClientConfig().getDigitalSignatureConfiguration());
bt = ds.signUddiEntity(bt);
} catch (Exception ex) {
log.error("Unable to sign", ex);
throw new UnableToSignException(ex);
}
break;
case SignOnlyIfParentIsntSigned:
if (!CheckServiceAndParentForSignature(bt.getServiceKey(), uddiInquiryService, token)) {
try {
DigSigUtil ds = new DigSigUtil(client.getClientConfig().getDigitalSignatureConfiguration());
bt = ds.signUddiEntity(bt);
} catch (Exception ex) {
log.error("Unable to sign", ex);
throw new UnableToSignException(ex);
}
}
break;
}
SaveBinding sb = new SaveBinding();
sb.setAuthInfo(token);
sb.getBindingTemplate().add(bt);
BindingDetail saveBinding = uddiPublishService.saveBinding(sb);
if (saveBinding.getBindingTemplate().isEmpty() || saveBinding.getBindingTemplate().size() > 1) {
throw new UnexpectedResponseException("The number of binding templates returned was unexpected, count=" + saveBinding.getBindingTemplate().size());
}
return saveBinding.getBindingTemplate().get(0);
}
protected static synchronized void unregisterAllCallbacks() {
if (callbacks != null) {
log.info("Notifying all subscribing classes, count=" + callbacks.size());
for (int i = 0; i < callbacks.size(); i++) {
if (callbacks.get(i) != null) {
try {
callbacks.get(i).notifyEndpointStopped();
} catch (Exception ex) {
log.warn("Your implementation on ISubscriptionCallback is faulty and threw an error, contact the developer", ex);
}
}
}
callbacks.clear();
}
}
/**
* This effectively stops the endpoint address and notifies all
* ISubscriptionCallback clients that the endpoint as been stopped.
* After it has been stopped, all ISubscriptionCallback are removed from
* the callback list. If the configuration file is set to automatically
* register binding templates, the binding template will be unregistered
* from the UDDI server
*
* @param client
* @param cfg_node_name
* @param bindingKey
* @throws org.apache.commons.configuration.ConfigurationException
*/
public static synchronized void stop(UDDIClient client, String cfg_node_name, String bindingKey) throws ConfigurationException {
//stop the service
if (ep != null && ep.isPublished()) {
log.warn("Stopping jUDDI Subscription callback endpoint at " + callback);
ep.stop();
if (ep.isPublished()) {
log.fatal("Unable to stop the endpoint. the port may be locked until this java process terminates");
}
ep = null;
callback = null;
}
unregisterAllCallbacks();
if (client.getClientConfig().getConfiguration().getBoolean(PROPERTY_AUTOREG_BT, false) && bindingKey != null) {
try {
UDDIClerk clerk = client.getClerk(cfg_node_name);
Transport tp = client.getTransport(cfg_node_name);
String token = clerk.getAuthToken(clerk.getUDDINode().getSecurityUrl());
UDDIPublicationPortType uddiPublishService = tp.getUDDIPublishService();
DeleteBinding db = new DeleteBinding();
db.setAuthInfo(token);
db.getBindingKey().add(bindingKey);
uddiPublishService.deleteBinding(db);
log.info("Subscription callback binding unregistered.");
} catch (Exception ex) {
log.error("Unable to unregister binding " + bindingKey, ex);
}
}
//TODO optionally kill the subscription?
//get all subscriptions from the uddi node,
//loop through and deduce which ones are pointed at this endpoint
//then remove them
}
private static String GetHostname() {
try {
return java.net.InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException ex) {
return "HOST_UNKNOWN";
}
}
@Override
public DispositionReport notifySubscriptionListener(NotifySubscriptionListener body) throws DispositionReportFaultMessage, RemoteException {
for (int i = 0; i < callbacks.size(); i++) {
try {
callbacks.get(i).handleCallback(body.getSubscriptionResultsList());
} catch (Exception ex) {
log.warn("Your implementation on ISubscriptionCallback is faulty and threw an error, contact the developer", ex);
}
}
DispositionReport r = new DispositionReport();
r.getResult().add(new Result());
return r;
}
}