org.hspconsortium.cwfdemo.api.eps.EPSService.java Source code

Java tutorial

Introduction

Here is the source code for org.hspconsortium.cwfdemo.api.eps.EPSService.java

Source

/*
 * #%L
 * EPS API
 * %%
 * Copyright (C) 2014 - 2016 Healthcare Services Platform Consortium
 * %%
 * 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.hspconsortium.cwfdemo.api.eps;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.ws.BindingProvider;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.carewebframework.api.thread.ThreadUtil;
import org.carewebframework.common.MiscUtil;

import org.hl7.fhir.instance.model.api.IBaseResource;
import org.socraticgrid.hl7.services.eps.accessclients.broker.BrokerServiceSE;
import org.socraticgrid.hl7.services.eps.accessclients.publication.PublicationServiceSE;
import org.socraticgrid.hl7.services.eps.accessclients.subscription.SubscriptionServiceSE;
import org.socraticgrid.hl7.services.eps.interfaces.BrokerIFace;
import org.socraticgrid.hl7.services.eps.interfaces.PublicationIFace;
import org.socraticgrid.hl7.services.eps.interfaces.SubscriptionIFace;
import org.socraticgrid.hl7.services.eps.model.AccessModel;
import org.socraticgrid.hl7.services.eps.model.Durability;
import org.socraticgrid.hl7.services.eps.model.Message;
import org.socraticgrid.hl7.services.eps.model.MessageBody;
import org.socraticgrid.hl7.services.eps.model.MessageHeader;
import org.socraticgrid.hl7.services.eps.model.Options;
import org.socraticgrid.hl7.services.eps.model.PullRange;
import org.socraticgrid.hl7.services.eps.model.SubscriptionType;
import org.socraticgrid.hl7.services.eps.model.User;

import ca.uhn.fhir.context.FhirContext;

/**
 *
 */
public class EPSService {

    public interface IEventCallback {

        void onEvent(Message event);
    }

    private class EventPoller extends Thread {

        private final Object monitor = new Object();

        private boolean terminate;

        /**
         * Wakes up the background thread.
         *
         * @return True if request was successful.
         */
        public synchronized boolean wakeup() {
            try {
                synchronized (monitor) {
                    monitor.notify();
                }
                return true;
            } catch (Throwable t) {
                return false;
            }
        }

        public void terminate() {
            terminate = true;
            wakeup();
        }

        @Override
        public void run() {
            synchronized (monitor) {
                while (!terminate) {
                    try {
                        pollEvents();
                        monitor.wait(pollingInterval);
                    } catch (InterruptedException e) {
                    }
                }
            }

            log.debug("Event poller has exited.");
        }

    }

    private class Subscription {

        private final Set<IEventCallback> callbacks = new HashSet<>();

        private final List<String> topic;

        private String subscriptionId;

        Subscription(String topic) {
            this.topic = Collections.singletonList(topic);
        }

        public synchronized boolean subscribe(IEventCallback callback) {
            if (subscriptionId == null) {
                subscribe();
            }

            return callbacks.add(callback);
        }

        public synchronized boolean unsubscribe(IEventCallback callback) {
            boolean result = callbacks.remove(callback);

            if (callbacks.isEmpty() && subscriptionId != null) {
                unsubscribe();
            }

            return result;
        }

        public boolean hasSubscribers() {
            return !callbacks.isEmpty();
        }

        public void deliverEvent(Message event) {
            Set<IEventCallback> cbs;

            synchronized (callbacks) {
                cbs = new HashSet<>(callbacks);
            }

            for (IEventCallback callback : cbs) {
                try {
                    callback.onEvent(event);
                } catch (Throwable e) {
                    log.error("Error during event delivery.", e);
                }
            }
        }

        private void subscribe() {
            Options options = new Options();
            options.setAccess(AccessModel.Open);
            options.setDurability(Durability.Transient);

            try {
                subscriptionId = getSubscriberPort().subscribe(topic, SubscriptionType.Pull, options, null);
            } catch (Exception e) {
                throw MiscUtil.toUnchecked(e);
            }
        }

        private void unsubscribe() {
            try {
                getSubscriberPort().unsubscribe(topic, subscriberId, subscriptionId);
            } catch (Exception e) {
                throw MiscUtil.toUnchecked(e);
            } finally {
                subscriptionId = null;
            }
        }
    }

    private static final Log log = LogFactory.getLog(EPSService.class);

    private final String subscriberId = UUID.randomUUID().toString();

    private final FhirContext fhirContext;

    private String serviceEndpoint;

    private User publisher;

    private PublicationIFace publisherPort;

    private SubscriptionIFace subscriberPort;

    private BrokerIFace brokerPort;

    private Date lastPoll = new Date();

    private final int pollingInterval = 5000;

    private final EventPoller eventPoller = new EventPoller();

    private final Map<String, Subscription> subscriptions = new ConcurrentHashMap<>();

    public EPSService(FhirContext fhirContext, String serviceEndpoint) {
        this.fhirContext = fhirContext;
        this.serviceEndpoint = serviceEndpoint;
    }

    public void init() {
        if (!serviceEndpoint.endsWith("/")) {
            serviceEndpoint += "/";
        }

        PublicationServiceSE ps = new PublicationServiceSE();
        publisherPort = ps.getPublicationPort();
        ((BindingProvider) publisherPort).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                serviceEndpoint + "publication");

        SubscriptionServiceSE ss = new SubscriptionServiceSE();
        subscriberPort = ss.getSubscriptionPort();
        ((BindingProvider) subscriberPort).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                serviceEndpoint + "subscriptionService");

        BrokerServiceSE bs = new BrokerServiceSE();
        brokerPort = bs.getBrokerPort();
        ((BindingProvider) brokerPort).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                serviceEndpoint + "broker");

        ThreadUtil.startThread(eventPoller);
    }

    public void destroy() {
        eventPoller.terminate();
    }

    public FhirContext getFhirContext() {
        return fhirContext;
    }

    public PublicationIFace getPublisherPort() {
        return publisherPort;
    }

    public SubscriptionIFace getSubscriberPort() {
        return subscriberPort;
    }

    public BrokerIFace getBrokerPort() {
        return brokerPort;
    }

    //***************************** Publication *****************************

    public User getPublisher() {
        return publisher;
    }

    /**
     * Raw event publication
     * 
     * @param topic Topic for publication.
     * @param event The event to be published.
     * @return The event id.
     */
    public String publishEvent(String topic, Message event) {
        if (event.getTopics().indexOf(topic) == -1) {
            event.getTopics().add(topic);
        }

        try {
            return publisherPort.publishEvent(topic, event);
        } catch (Exception e) {
            throw MiscUtil.toUnchecked(e);
        }
    }

    /**
     * Raw event publication driven by topics declared in the message
     * 
     * @param event The event to be published.
     * @return A map of event id's keyed by topic.
     */
    public Map<String, String> publishEvent(Message event) {
        Map<String, String> msgIds = new HashMap<>();

        for (String topic : event.getTopics()) {
            msgIds.put(topic, publishEvent(topic, event));
        }

        return msgIds;
    }

    /**
     * Compose and publish an event on a topic
     * 
     * @param topic Topic for publication.
     * @param data Data to be published.
     * @param contentType The content type of the data.
     * @param subject Event subject.
     * @param title Event title.
     * @return The event id.
     */
    public String publishEvent(String topic, String data, String contentType, String subject, String title) {
        Date now = new Date();

        Message event = new Message();

        MessageHeader header = event.getHeader();
        header.setMessageId(UUID.randomUUID().toString()); // Not sure we need to generate an Id
        header.setTopicId(topic);
        header.setSubject(subject);
        header.setMessageCreatedTime(now);
        header.setMessagePublicationTime(now);
        header.setPublisher(publisher);
        event.setTitle(title);

        MessageBody body = new MessageBody();
        body.setType(contentType);
        body.setBody(data);
        event.getMessageBodies().add(body);

        return publishEvent(topic, event);
    }

    /**
     * Publish a FHIR resource to a topic (as JSON) using a default subject & title
     * 
     * @param topic Topic for publication.
     * @param resource The FHIR resource to be published.
     * @return The event id.
     */
    public String publishResourceToTopic(String topic, IBaseResource resource) {
        return publishResourceToTopic(topic, resource, "FHIR Resource", resource.getClass().getName());
    }

    /**
     * Publish a FHIR resource to a topic, proving a title and subject
     * 
     * @param topic Topic for publication.
     * @param resource The FHIR resource to be published.
     * @param subject Event subject.
     * @param title Event title.
     * @return The event id.
     */
    public String publishResourceToTopic(String topic, IBaseResource resource, String subject, String title) {
        String data = fhirContext.newJsonParser().encodeResourceToString(resource);
        return publishEvent(topic, data, "application/json+fhir", subject, title);
    }

    //**************************** Subscription *****************************

    private Subscription getSubscription(String topic, boolean forceCreate) {
        Subscription subscription = subscriptions.get(topic);

        if (subscription == null && forceCreate) {
            synchronized (subscriptions) {
                subscriptions.put(topic, subscription = new Subscription(topic));
            }
        }

        return subscription;
    }

    public boolean subscribe(String topic, IEventCallback callback) {
        Subscription subscription = getSubscription(topic, true);
        return subscription.subscribe(callback);
    }

    public boolean unsubscribe(String topic, IEventCallback callback) {
        Subscription subscription = getSubscription(topic, false);
        return subscription != null && subscription.unsubscribe(callback);
    }

    private void pollEvents() {
        Date start = lastPoll;
        Date end = new Date();
        lastPoll = end;

        for (Entry<String, Subscription> entry : subscriptions.entrySet()) {
            try {
                if (!entry.getValue().hasSubscribers()) {
                    continue;
                }

                List<Message> events = getSubscriberPort().retrieveEvents(entry.getKey(), PullRange.Specific, start,
                        end, Collections.<String>emptyList());

                for (Message event : events) {
                    entry.getValue().deliverEvent(event);
                }
            } catch (Exception e) {
                log.error("Exception while polling for events.", e);
            }
        }
    }

}