blob: 52f3fd7cbe0c86e0e0b19f618349df6428de59a8 [file] [log] [blame]
/*
* Copyright 2001-2008 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.
*
*/
using org.apache.juddi.client.org.apache.juddi.v3.client.subscription;
using org.apache.juddi.v3.client.config;
using org.apache.juddi.v3.client.cryptor;
using org.apache.juddi.v3.client.log;
using org.apache.juddi.v3.client.subscription.wcf;
using org.apache.juddi.v3.client.transport;
using org.uddi.apiv3;
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using System.Security;
using System.ServiceModel;
namespace org.apache.juddi.v3.client.subscription
{
/// <summary>
///
/// 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.
/// </summary>
/// &lt;h1&gt;Usage scenario&lt;/h1&gt;
/// 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&#39;s some sample code.
/// <example>
/// <code>
/// &lt;pre&gt;
/// UDDIClient c = new UDDIClient(&quot;META-INF/uddiclient.xml&quot;);
/// UDDIClerk clerk = c.getClerk(&quot;default&quot;);
/// TModel createKeyGenator = UDDIClerk.createKeyGenator(&quot;uddi:org.apache.juddi:test:keygenerator&quot;, &quot;Test domain&quot;, &quot;en&quot;);
/// clerk.register(createKeyGenator);
/// BindingTemplate start = SubscriptionCallbackListener.start(c, &quot;default&quot;);
/// //keep alive
/// while(running)
/// Thread.sleep(1000);
/// SubscriptionCallbackListener.stop(c, &quot;default&quot;, start.getBindingKey());
/// &lt;/pre&gt;
/// </code></example>
/// @author &lt;a href=&quot;mailto:alexoree@apache.org&quot;&gt;Alex O&#39;Ree&lt;/a&gt;
/// @since 3.2
[ServiceBehaviorAttribute(AutomaticSessionShutdown = false, ConcurrencyMode = ConcurrencyMode.Single,
//DOES NOT WORK ON MONO
// Name = "SubscriptionCallbackListener",
// Namespace = "org.apache.juddi.v3.client.subscription",
IncludeExceptionDetailInFaults = false, InstanceContextMode = InstanceContextMode.Single, ValidateMustUnderstand = false
// ,AddressFilterMode = AddressFilterMode.Any
)]
public class SubscriptionCallbackListener : UDDI_SubscriptionListener_PortType
{
public SubscriptionCallbackListener()
{
AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
}
protected static SubscriptionCallbackListener getInstance()
{
return instance;
}
private static Log log = LogFactory.getLog(typeof(SubscriptionCallbackListener));
private static List<ISubscriptionCallback> callbacks = new List<ISubscriptionCallback>();
private static SubscriptionCallbackListener instance = null;
private static ServiceHost ep = null;
private static String callback = null;
/**
* gets the current callback url, may be null if the endpoint isn't started
* yet
*
* @return
*/
public static String getCallbackURL()
{
return callback;
}
/**
* Starts a embedded WCF web server (comes with .NET) using the
* Endpoint API.
*
* @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 callbackBusinessService - optional. if specified, a binding
* template is appended to the business service and returned. The new
* binding template is annotated for subscription callbacks.
* @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
* @see Endpoint
*/
[MethodImpl(MethodImplOptions.Synchronized)]
public static bindingTemplate start(UDDIClient client, String cfg_node_name, String endpoint,
String keydomain, bool autoregister, String serviceKey,
SignatureBehavior behavior)
{
if (instance == null)
{
instance = new SubscriptionCallbackListener();
}
if (ep != null && ep.State == CommunicationState.Opened)
{
throw new ServiceAlreadyStartedException();
}
Uri url = null;
try
{
url = new Uri(endpoint);
}
catch (Exception ex)
{
log.warn("Callback endpoint couldn't be parsed, generating a random one: " + ex.Message);
url = new Uri("http://" + GetHostname() + ":" + GetRandomPort(4000) + "/" + Guid.NewGuid().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.State != CommunicationState.Opened) && attempts > 0)
{
try
{
if (endpoint.Contains("localhost"))
endpoint = endpoint.Replace("localhost", GetHostname());
ep = new ServiceHost(instance, new Uri[] { new Uri(endpoint) });
//ep = Endpoint.publish(endpoint, instance);
ep.Open();
callback = endpoint;
}
catch (Exception be)
{
log.info("trouble starting callback at " + endpoint + ", trying again with a random port: " + be.Message);
log.debug(be);
attempts--;
//if (be instanceof java.net.BindException) {
url = new Uri("http://" + url.Host + ":" + GetRandomPort(url.Port) + "/" + url.PathAndQuery);
endpoint = url.ToString();
}
}
}
if (ep == null || ep.State != CommunicationState.Opened)
{
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.Item = (new accessPoint(callback, "endPoint"));
tModelInstanceInfo instanceInfo = new tModelInstanceInfo();
instanceInfo.tModelKey = ("uddi:uddi.org:transport:http");
bt.tModelInstanceDetails = new tModelInstanceInfo[] { instanceInfo };
bt.serviceKey = (serviceKey);
if (keydomain.EndsWith(":"))
{
bt.bindingKey = (keydomain + GetHostname() + "_Subscription_Callback");
}
else
{
bt.bindingKey = (keydomain + ":" + GetHostname() + "_Subscription_Callback");
}
if (autoregister)
{
bt = registerBinding(client, cfg_node_name, bt, behavior);
}
return bt;
}
private static int GetRandomPort(int oldport)
{
if (oldport <= 0)
{
oldport = 4000;
}
return oldport + new Random().Next(99);
}
/// <summary>
/// Starts a subscription callback service using the juddi client config
/// file's settings
/// </summary>
/// <param name="client"></param>
/// <param name="cfg_node_name"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.Synchronized)]
public static bindingTemplate start(UDDIClient client, String cfg_node_name)
{
bool reg = client.getClientConfig().getConfiguration().client.subscriptionCallbacks.autoRegisterBindingTemplate;
String endpoint = client.getClientConfig().getConfiguration().client.subscriptionCallbacks.listenUrl;
String kd = client.getClientConfig().getConfiguration().client.subscriptionCallbacks.keyDomain;
String key = client.getClientConfig().getConfiguration().client.subscriptionCallbacks.autoRegisterBusinessServiceKey;
String sbs = client.getClientConfig().getConfiguration().client.subscriptionCallbacks.signatureBehavior;
SignatureBehavior sb = SignatureBehavior.DoNothing;
try
{
sb = (SignatureBehavior)Enum.Parse(typeof(SignatureBehavior), 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);
}
/**
* Registers a UDDI binding template that represents the subscription
* callback endpoint
*
* @param client
* @param cfg_node_name
* @param bt - Binding Template
* @param behavior
* @return
* @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)
{
log.info("Attempting to register binding " + bt.bindingKey);
UDDIClerk clerk = client.getClerk(cfg_node_name);
Transport tp = client.getTransport(cfg_node_name);
UDDI_Inquiry_SoapBinding uddiInquiryService = tp.getUDDIInquiryService();
UDDI_Publication_SoapBinding uddiPublishService = tp.getUDDIPublishService();
String token = clerk.getAuthToken(clerk.getUDDINode().getSecurityUrl());
switch (behavior)
{
case SignatureBehavior.AbortIfSigned:
if (CheckExistingBindingForSignature(bt.bindingKey, uddiInquiryService, token, behavior))
{
throw new RegistrationAbortedException("Aborting, Either the item exists and is signed");
}
if (CheckServiceAndParentForSignature(bt.serviceKey, uddiInquiryService, token))
{
throw new RegistrationAbortedException("Aborting, Either the service or busness is signed");
}
break;
case SignatureBehavior.DoNothing:
break;
case SignatureBehavior.SignAlways:
try
{
DigSigUtil ds = new DigSigUtil(client.getClientConfig().getDigitalSignatureConfiguration());
bt = (bindingTemplate)ds.signUddiEntity(bt);
}
catch (Exception ex)
{
log.error("Unable to sign", ex);
throw new UnableToSignException("Unable to sign", ex);
}
break;
case SignatureBehavior.SignOnlyIfParentIsntSigned:
if (!CheckServiceAndParentForSignature(bt.serviceKey, uddiInquiryService, token))
{
try
{
DigSigUtil ds = new DigSigUtil(client.getClientConfig().getDigitalSignatureConfiguration());
bt = (bindingTemplate)ds.signUddiEntity(bt);
}
catch (Exception ex)
{
log.error("Unable to sign", ex);
throw new UnableToSignException("Unable to sign", ex);
}
}
break;
}
save_binding sb = new save_binding();
sb.authInfo = (token);
sb.bindingTemplate = new bindingTemplate[] { bt };
bindingDetail saveBinding = uddiPublishService.save_binding(sb);
log.info("Binding registered successfully");
if (saveBinding.bindingTemplate == null || saveBinding.bindingTemplate.Length > 1)
{
throw new UnexpectedResponseException("The number of binding templates returned was unexpected, count=" + (saveBinding.bindingTemplate == null ? "none" : saveBinding.bindingTemplate.Length.ToString()));
}
return saveBinding.bindingTemplate[0];
}
private static bool CheckServiceAndParentForSignature(String serviceKey, UDDI_Inquiry_SoapBinding uddiInquiryService, String token)
{
get_serviceDetail gsd = new get_serviceDetail();
gsd.authInfo = (token);
gsd.serviceKey = new string[] { serviceKey };
String bizkey = null;
try
{
serviceDetail serviceDetail = uddiInquiryService.get_serviceDetail(gsd);
if (serviceDetail != null && serviceDetail.businessService != null)
{
{
bizkey = serviceDetail.businessService[0].businessKey;
if (serviceDetail.businessService[0].Signature != null && serviceDetail.businessService[0].Signature.Length > 0)
{
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");
}
get_businessDetail gbd = new get_businessDetail();
gbd.authInfo = (token);
gbd.businessKey = new string[] { bizkey };
try
{
businessDetail businessDetail = uddiInquiryService.get_businessDetail(gbd);
if (businessDetail != null && businessDetail.businessEntity != null)
{
if (businessDetail.businessEntity[0].Signature != null && businessDetail.businessEntity[0].Signature.Length > 0)
{
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;
}
/**
* return true if and only if the binding exists and is signed
*
* @param bindingKey
* @param uddiInquiryService
* @param token
* @param behavior
* @return
*/
private static bool CheckExistingBindingForSignature(String bindingKey, UDDI_Inquiry_SoapBinding uddiInquiryService, String token, SignatureBehavior behavior)
{
get_bindingDetail gbd = new get_bindingDetail();
gbd.authInfo = (token);
gbd.bindingKey = new string[] { bindingKey };
try
{
bindingDetail bindingDetail = uddiInquiryService.get_bindingDetail(gbd);
if (bindingDetail != null
&& bindingDetail.bindingTemplate != null
&& bindingDetail.bindingTemplate.Length > 0
&& bindingDetail.bindingTemplate[0].Signature != null
&& bindingDetail.bindingTemplate[0].Signature.Length > 0)
{
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 void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
if (ep != null && ep.State == CommunicationState.Opened)
{
log.error("Hey, someone should tell the developer to call SubscriptionCallbackListern.stop(...) before ending the program. Stopping endpoint at " + callback);
unregisterAllCallbacks();
ep.Close();
ep = null;
callback = null;
}
}
public uddi.apiv3.dispositionReport notify_subscriptionListener(uddi.apiv3.notify_subscriptionListener body)
{
for (int i = 0; i < callbacks.Count; i++)
{
try
{
callbacks[i].HandleCallback(body.subscriptionResultsList);
}
catch (Exception ex)
{
log.warn("Your implementation on ISubscriptionCallback is faulty and threw an error, contact the developer", ex);
}
}
dispositionReport r = new dispositionReport();
r.result = new result[] { new result() };
return r;
}
[MethodImpl(MethodImplOptions.Synchronized)]
protected static void unregisterAllCallbacks()
{
if (callbacks != null)
{
log.info("Notifying all subscribing classes, count=" + callbacks.Count);
for (int i = 0; i < callbacks.Count; i++)
{
if (callbacks[(i)] != null)
{
try
{
callbacks[(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, call ISubscriptionCallback are removed from the
* callback list. If unable to remove an auto registered binding template,
* no exception will be thrown
*/
[MethodImpl(MethodImplOptions.Synchronized)]
public static void stop(UDDIClient client, String cfg_node_name, String bindingKey)
{
//stop the service
if (ep != null && ep.State == CommunicationState.Opened)
{
log.warn("Stopping jUDDI Subscription callback endpoint at " + callback);
ep.Close();
if (ep.State != CommunicationState.Closed && ep.State != CommunicationState.Closing)
{
log.error("Unable to stop the endpoint. the port may be locked until this process terminates");
}
ep = null;
callback = null;
}
unregisterAllCallbacks();
if (client.getClientConfig().getConfiguration().client != null &&
client.getClientConfig().getConfiguration().client.subscriptionCallbacks != null &&
client.getClientConfig().getConfiguration().client.subscriptionCallbacks.signatureBehavior != null &&
client.getClientConfig().getConfiguration().client.subscriptionCallbacks.signatureBehavior.Equals("true", StringComparison.CurrentCultureIgnoreCase) &&
bindingKey != null)
{
log.info("Attempting to unregister the binding");
try
{
UDDIClerk clerk = client.getClerk(cfg_node_name);
Transport tp = client.getTransport(cfg_node_name);
String token = clerk.getAuthToken(clerk.getUDDINode().getSecurityUrl());
UDDI_Publication_SoapBinding uddiPublishService = tp.getUDDIPublishService();
delete_binding db = new delete_binding();
db.authInfo = (token);
db.bindingKey = new string[] { (bindingKey) };
uddiPublishService.delete_binding(db);
}
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 Dns.GetHostName();
}
catch (Exception)
{
return "HOST_UNKNOWN";
}
}
/**
* config parameter
*/
public static readonly String PROPERTY_LISTENURL = "client.subscriptionCallbacks.listenUrl";
/**
* config parameter
*/
public static readonly String PROPERTY_KEYDOMAIN = "client.subscriptionCallbacks.keyDomain";
/**
* config parameter true/false
*/
public static readonly String PROPERTY_AUTOREG_BT = "client.subscriptionCallbacks.autoRegisterBindingTemplate";
/**
* config parameter business key
*/
public static readonly String PROPERTY_AUTOREG_SERVICE_KEY = "client.subscriptionCallbacks.autoRegisterBusinessServiceKey";
/**
* config parameter
*
* @see SignatureBehavior
*/
public static readonly String PROPERTY_SIGNATURE_BEHAVIOR = "client.subscriptionCallbacks.signatureBehavior";
}
/**
* 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
}
}