/* * 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 { /// /// /// 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 [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 callbacks = new List(); 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); } /// /// Starts a subscription callback service using the juddi client config /// file's settings /// /// /// /// [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 } }