/* * The MIT License * * Copyright 2010 Sony Ericsson Mobile Communications. All rights reserved. * Copyright 2012 Sony Mobile Communications AB. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.sonymobile.tools.gerrit.gerritevents; import com.sonymobile.tools.gerrit.gerritevents.dto.GerritEvent; import com.sonymobile.tools.gerrit.gerritevents.dto.attr.Account; import com.sonymobile.tools.gerrit.gerritevents.dto.attr.Provider; import com.sonymobile.tools.gerrit.gerritevents.dto.events.CommentAdded; import com.sonymobile.tools.gerrit.gerritevents.workers.Coordinator; import com.sonymobile.tools.gerrit.gerritevents.workers.EventThread; import com.sonymobile.tools.gerrit.gerritevents.workers.GerritEventWork; import com.sonymobile.tools.gerrit.gerritevents.workers.JSONEventWork; import com.sonymobile.tools.gerrit.gerritevents.workers.StreamEventsStringWork; import com.sonymobile.tools.gerrit.gerritevents.workers.Work; import net.sf.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.LinkedBlockingQueue; //CS IGNORE LineLength FOR NEXT 1 LINES. REASON: static import. import static com.sonymobile.tools.gerrit.gerritevents.GerritDefaultValues.DEFAULT_NR_OF_RECEIVING_WORKER_THREADS; /** * Main class for this module. Contains the main loop for connecting and reading streamed events from Gerrit. * * @author Robert Sandell <robert.sandell@sonyericsson.com> */ public class GerritHandler implements Coordinator, Handler { /** * Time to wait between connection attempts. */ private static final Logger logger = LoggerFactory.getLogger(GerritHandler.class); private BlockingQueue<Work> workQueue; private int numberOfWorkerThreads; private final Set<GerritEventListener> gerritEventListeners = new CopyOnWriteArraySet<GerritEventListener>(); private final List<EventThread> workers; private Map<String, String> ignoreEMails = new ConcurrentHashMap<String, String>(); /** * Creates a GerritHandler with all the default values set. * * @see GerritDefaultValues#DEFAULT_NR_OF_RECEIVING_WORKER_THREADS */ public GerritHandler() { this(DEFAULT_NR_OF_RECEIVING_WORKER_THREADS); } /** * Standard Constructor. * * @param numberOfWorkerThreads the number of event threads. */ public GerritHandler(int numberOfWorkerThreads) { this.numberOfWorkerThreads = numberOfWorkerThreads; workQueue = new LinkedBlockingQueue<Work>(); workers = new ArrayList<EventThread>(numberOfWorkerThreads); for (int i = 0; i < numberOfWorkerThreads; i++) { EventThread eventThread = createEventThread("Gerrit Worker EventThread_" + i); eventThread.start(); workers.add(eventThread); } } /** * Create the Event Thread. * @param threadName Name of thread to be created. * @return new EventThread to be used by worker */ protected EventThread createEventThread(String threadName) { return new EventThread(this, threadName); } /** * Standard getter for the ignoreEMail. * * @param serverName the server name. * @return the e-mail address to ignore CommentAdded events from. */ public String getIgnoreEMail(String serverName) { if (serverName != null) { return ignoreEMails.get(serverName); } else { return null; } } /** * Standard setter for the ignoreEMail. * * @param serverName the server name. * @param ignoreEMail the e-mail address to ignore CommentAdded events from. * If you want to remove, please set null. */ public void setIgnoreEMail(String serverName, String ignoreEMail) { if (serverName != null) { if (ignoreEMail != null) { ignoreEMails.put(serverName, ignoreEMail); } else { ignoreEMails.remove(serverName); } } } @Override public void post(String data) { post(data, null); } @Override public void post(JSONObject json) { post(json, null); } @Override public void post(String data, Provider provider) { logger.debug("Trigger event string: {}", data); post(new StreamEventsStringWork(data, provider)); } @Override public void post(JSONObject json, Provider provider) { logger.debug("Trigger event json object: {}", json); post(new JSONEventWork(json, provider)); } @Override public void post(GerritEvent event) { logger.debug("Internally trigger event: {}", event); post(new GerritEventWork(event)); } /** * Post work object to work queue. * * @param work the work object. */ private void post(Work work) { try { logger.trace("putting work on queue."); workQueue.put(work); } catch (InterruptedException ex) { logger.warn("Interrupted while putting work on queue!", ex); //TODO check if shutdown //TODO try again since it is important } } @Override public void addListener(GerritEventListener listener) { synchronized (this) { if (!gerritEventListeners.add(listener)) { logger.warn("The listener was doubly-added: {}", listener); } } } /** * Adds all the provided listeners to the internal list of listeners. * * @param listeners the listeners to add. */ @Deprecated public void addEventListeners(Map<Integer, GerritEventListener> listeners) { addEventListeners(listeners.values()); } /** * Adds all the provided listeners to the internal list of listeners. * * @param listeners the listeners to add. */ public void addEventListeners(Collection<? extends GerritEventListener> listeners) { synchronized (this) { gerritEventListeners.addAll(listeners); } } @Override public void removeListener(GerritEventListener listener) { synchronized (this) { gerritEventListeners.remove(listener); } } /** * Removes all event listeners and returns those that where removed. * * @return the former list of listeners. */ public Collection<GerritEventListener> removeAllEventListeners() { synchronized (this) { HashSet<GerritEventListener> listeners = new HashSet<GerritEventListener>(gerritEventListeners); gerritEventListeners.clear(); return listeners; } } /** * The number of added e{@link GerritEventListener}s. * @return the size. */ public int getEventListenersCount() { return gerritEventListeners.size(); } /** * Returns an unmodifiable view of the set of {@link GerritEventListener}s. * * @return a list of the registered event listeners. * @see Collections#unmodifiableSet(Set) */ public Set<GerritEventListener> getGerritEventListenersView() { return Collections.unmodifiableSet(gerritEventListeners); } /** * Gets the number of event worker threads. * * @return the number of threads. */ public int getNumberOfWorkerThreads() { return numberOfWorkerThreads; } /** * Sets the number of worker event threads. * * @param numberOfWorkerThreads the number of threads */ public void setNumberOfWorkerThreads(int numberOfWorkerThreads) { this.numberOfWorkerThreads = numberOfWorkerThreads; //TODO what if nr of workers are increased/decreased in runtime. } @Override public BlockingQueue<Work> getWorkQueue() { return workQueue; } /** * Notifies all listeners of a Gerrit event. This method is meant to be called by one of the Worker Threads {@link * com.sonymobile.tools.gerrit.gerritevents.workers.EventThread} and not on this Thread which would * defeat the purpose of having workers. * * @param event the event. */ @Override public void notifyListeners(GerritEvent event) { if (event instanceof CommentAdded) { if (ignoreEvent((CommentAdded)event)) { logger.trace("CommentAdded ignored"); return; } } for (GerritEventListener listener : gerritEventListeners) { try { notifyListener(listener, event); } catch (Exception ex) { logger.error("When notifying listener: {} about event: {}", listener, event); logger.error("Notify-error: ", ex); } } } /** * Sub method of {@link #notifyListeners(com.sonymobile.tools.gerrit.gerritevents.dto.GerritEvent) }. * This is where most of the reflection magic in the event notification is done. * * @param listener the listener to notify * @param event the event. */ private void notifyListener(GerritEventListener listener, GerritEvent event) { logger.trace("Notifying listener {} of event {}", listener, event); try { logger.trace("Reflecting closest method"); Method method = listener.getClass().getMethod("gerritEvent", event.getClass()); method.invoke(listener, event); } catch (IllegalAccessException ex) { logger.debug("Not allowed to invoke the reflected method. Calling default.", ex); listener.gerritEvent(event); } catch (IllegalArgumentException ex) { logger.debug("Not allowed to invoke the reflected method with specified parameter (REFLECTION BUG). " + "Calling default.", ex); listener.gerritEvent(event); } catch (InvocationTargetException ex) { logger.error("Exception thrown during event handling.", ex); } catch (NoSuchMethodException ex) { logger.debug("No apropriate method found during reflection. Calling default.", ex); listener.gerritEvent(event); } catch (SecurityException ex) { logger.debug("Not allowed to reflect/invoke a method on this listener (DESIGN BUG). Calling default", ex); listener.gerritEvent(event); } } /** * Checks if the event should be ignored, due to a circular CommentAdded. * @param event the event to check. * @return true if it should be ignored, false if not. */ private boolean ignoreEvent(CommentAdded event) { Account account = event.getAccount(); if (account == null) { return false; } Provider provider = event.getProvider(); if (provider != null) { String ignoreEMail = ignoreEMails.get(provider.getName()); if (ignoreEMail != null && ignoreEMail.equals(account.getEmail())) { return true; } } return false; } /** * Closes the handler. * * @param join if the method should wait for the thread to finish before returning. */ public void shutdown(boolean join) { for (EventThread worker : workers) { worker.shutdown(); } workers.clear(); } /** * "Triggers" an event by adding it to the internal queue and be taken by one of the worker threads. This way it * will be put into the normal flow of events as if it was coming from the stream-events command. * * @param event the event to trigger. */ @Deprecated public void triggerEvent(GerritEvent event) { logger.debug("Internally trigger event: {}", event); try { logger.trace("putting work on queue."); workQueue.put(new GerritEventWork(event)); } catch (InterruptedException ex) { logger.error("Interrupted while putting work on queue!", ex); } } }