Java tutorial
/** * Copyright 2014 Otto (GmbH & Co KG) * * 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 com.ottogroup.bi.asap.component.source.executor; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.ottogroup.bi.asap.component.source.Source; import com.ottogroup.bi.asap.component.strategy.MessageWaitStrategy; import com.ottogroup.bi.asap.component.strategy.YieldMessageWaitStrategy; import com.ottogroup.bi.asap.exception.RequiredInputMissingException; import com.ottogroup.bi.asap.exception.handler.ComponentExceptionHandler; import com.ottogroup.bi.asap.exception.handler.ExecutorExceptionHandler; import com.ottogroup.bi.asap.mailbox.Mailbox; import com.ottogroup.bi.asap.message.StreamingDataMessage; /** * Provides a runtime environment for {@link Source} instances * @author mnxfst * @since Dec 15, 2014 * TODO create a executor service for this executor and the source * TODO testing */ public class SourceExecutor implements Runnable { private static final Logger logger = Logger.getLogger(SourceExecutor.class); /** fixed thread pool suffice as we start only one thread */ private final ExecutorService sourceExecutorService = Executors.newCachedThreadPool(); private final Source source; private final Mailbox mailbox; private final Map<String, Mailbox> subscriberMailboxes = new HashMap<>(); private final MessageWaitStrategy messageWaitStrategy; private final ExecutorExceptionHandler executorExceptionHandler; private final ComponentExceptionHandler componentExceptionHandler; private boolean running = false; /** * Initializes the executor using the provided input * @param source * @param messageWaitStrategy * @param executorExceptionHandler * @param componentExceptionHandler * @param mailbox mailbox provided to {@link Source} to receive {@link StreamingDataMessage messages} * @throws RequiredInputMissingException */ public SourceExecutor(final Source source, final MessageWaitStrategy messageWaitStrategy, final ExecutorExceptionHandler executorExceptionHandler, final ComponentExceptionHandler componentExceptionHandler, final Mailbox mailbox) throws RequiredInputMissingException { /////////////////////////////////////////////////////////////////// // validate input if (source == null) throw new RequiredInputMissingException("Missing required source instance"); if (mailbox == null) throw new RequiredInputMissingException("Missing required mailbox"); // /////////////////////////////////////////////////////////////////// if (messageWaitStrategy != null) this.messageWaitStrategy = messageWaitStrategy; else this.messageWaitStrategy = new YieldMessageWaitStrategy(); this.source = source; this.running = true; this.executorExceptionHandler = executorExceptionHandler; this.componentExceptionHandler = componentExceptionHandler; this.mailbox = mailbox; this.source.setMailbox(mailbox); this.sourceExecutorService.submit(this.source); if (logger.isDebugEnabled()) logger.debug("source executor initalized [id=" + source.getId() + ", running=" + running + ",mailbox=" + mailbox + "]"); } /** * Adds the provided {@link Mailbox} as receiver of all {@link StreamingDataMessage} items received by the * underlying {@link Source}. The given identifier references the linked mailbox. * @param adaptorId * @param messageSubscriber */ public void subscribe(final String subscriberId, final Mailbox subscriberMailbox) throws RequiredInputMissingException { /////////////////////////////////////////////////////////////////////////// // validate input if (StringUtils.isBlank(subscriberId)) throw new RequiredInputMissingException("Missing required subscriber identifier"); if (subscriberMailbox == null) throw new RequiredInputMissingException("Missing required subscriber mailbox"); // /////////////////////////////////////////////////////////////////////////// this.subscriberMailboxes.put(StringUtils.trim(StringUtils.lowerCase(subscriberId)), subscriberMailbox); if (logger.isDebugEnabled()) logger.debug("subscribe[publisher=" + source.getId() + ", subscriber=" + subscriberId + ", mailbox=" + subscriberMailbox + "]"); } /** * Un-subscribes the referenced subscriber from operator * @param subscriberId * @throws RequiredInputMissingException */ public void unsubscribe(final String subscriberId) throws RequiredInputMissingException { /////////////////////////////////////////////////////////////////////////// // validate input if (StringUtils.isBlank(subscriberId)) throw new RequiredInputMissingException("Missing required subscriber identifier"); // /////////////////////////////////////////////////////////////////////////// this.subscriberMailboxes.remove(StringUtils.trim(StringUtils.lowerCase(subscriberId))); } /** * Returns true if the referenced component is a subscriber to this source * @param subscriberId * @return * @throws RequiredInputMissingException */ public boolean isSubscriber(final String subscriberId) throws RequiredInputMissingException { /////////////////////////////////////////////////////////////////////////// // validate input if (StringUtils.isBlank(subscriberId)) throw new RequiredInputMissingException("Missing required subscriber identifier"); // /////////////////////////////////////////////////////////////////////////// return this.subscriberMailboxes.containsKey(StringUtils.trim(StringUtils.lowerCase(subscriberId))); } /** * @see java.lang.Runnable#run() */ public void run() { // runs until it is stopped by calling shutdown() while (running) { // read next element from mailbox and provide it to the filter if // not null otherwise apply the assigned wait strategy and (hopefully) // free computational resources for next thread ;-) try { StreamingDataMessage message = this.mailbox.next(); if (message != null) { try { for (final Mailbox subscriberMailbox : this.subscriberMailboxes.values()) { subscriberMailbox.insert(message); } } catch (Exception e) { if (this.componentExceptionHandler != null) this.componentExceptionHandler.handleComponentException(e); e.printStackTrace(); } } else { try { messageWaitStrategy.waitFor(); } catch (InterruptedException e) { logger.info("Thread #" + Thread.currentThread().getId() + " interrupted while waiting for next mailbox message. Reason: " + e.getMessage()); } } } catch (Exception e) { if (this.executorExceptionHandler != null) this.executorExceptionHandler.handleExecutorException(e); } } try { this.source.shutdown(); } catch (Exception e) { if (this.executorExceptionHandler != null) this.executorExceptionHandler.handleExecutorException(e); } } /** * Returns the identifier of the underlying {@link Source} * @return */ public String getSourceId() { return this.source.getId(); } /** * Shuts down the executor */ public void shutdown() { this.running = false; } /** * Returns the map holding all subscribed {@link Mailbox mailboxes} * @return */ public Map<String, Mailbox> getSubscriberMailboxes() { return subscriberMailboxes; } /** * Indicates the running state of the executor * @return */ public boolean isRunning() { return running; } }