/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.stanbol.entityhub.indexing.core.impl; import static org.apache.stanbol.entityhub.indexing.core.impl.IndexerConstants.ERROR_TIME; import static org.apache.stanbol.entityhub.indexing.core.impl.IndexerConstants.INDEXING_COMPLETED_QUEUE_ITEM; import java.util.Collections; import java.util.EventObject; import java.util.HashSet; import java.util.Set; import java.util.concurrent.BlockingQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class IndexingDaemon<CI,PI> implements Comparable<IndexingDaemon<?,?>>,Runnable { protected final Logger log; private boolean queueFinished = false; private final BlockingQueue<QueueItem<CI>> consume; private final BlockingQueue<QueueItem<PI>> produce; private final BlockingQueue<QueueItem<IndexingError>> error; private boolean finisehd = false; private final Set<IndexingDaemonListener> listeners; /** * Typically used to set the name of the {@link Thread} running this Runnable */ private final String name; /** * Used for {@link #compareTo(IndexingDaemon)} */ private final Integer sequence; protected IndexingDaemon(String name, Integer sequence, BlockingQueue<QueueItem<CI>> consume, BlockingQueue<QueueItem<PI>> produce, BlockingQueue<QueueItem<IndexingError>> error){ if(name == null || name.isEmpty()){ this.name = getClass().getSimpleName()+" Deamon"; } else { this.name = name; } if(sequence == null){ this.sequence = Integer.valueOf(0); } else { this.sequence = sequence; } this.consume = consume; this.produce = produce; this.error = error; //get the logger for the actual implementation this.log = LoggerFactory.getLogger(getClass()); this.listeners = Collections.synchronizedSet( new HashSet<IndexingDaemonListener>()); } protected final void sendError(String entityId, String message, Exception ex){ if(entityId == null){ return; } else { putError(new QueueItem<IndexingError>( new IndexingError(entityId, message, ex))); } } protected final void sendError(String entityId,QueueItem<?> item, String message, Exception ex){ if(entityId == null){ return; } putError((new QueueItem<IndexingError>( new IndexingError(entityId, message, ex) ,item))); } private void putError(QueueItem<IndexingError> errorItem){ if(error == null){ log.warn("Unable to process Error because Error Queue is NULL!"); } Long errorTime = Long.valueOf(System.currentTimeMillis()); errorItem.setProperty(ERROR_TIME, errorTime); try { error.put(errorItem); } catch (InterruptedException e) { log.error("Interupped while sending an Error for Entity "+errorItem.getItem().getEntity()); } } protected final void produce(QueueItem<PI> item){ if(produce == null){ log.warn("Unable to produce Items because produce queue is NULL!"); } if(item != null){ try { produce.put(item); } catch (InterruptedException e) { log.error("Interupped while producing item "+item.getItem(), e); } } } protected final QueueItem<CI> consume(){ if(queueFinished){ return null; } if(consume == null){ log.warn("Unable to consume items because consume queue is NULl!"); } try { QueueItem<CI> consumed = consume.take(); if(consumed == INDEXING_COMPLETED_QUEUE_ITEM){ queueFinished = true; consume.put(consumed); //put it back to the list return null; } else { return consumed; } } catch (InterruptedException e) { log.error("Interupped while consuming -> return null"); return null; } } /** * @return the queueFinished */ protected final boolean isQueueFinished() { return queueFinished; } /** * Method has to be called by the subclasses to signal that this Runnable * has finished. It will set {@link #finished()} to <code>true</code> */ protected final void setFinished(){ this.finisehd = true; //tell listener that his one has finished! fireIndexingDaemonEvent(); } public final boolean finished(){ return finisehd; } public final boolean addIndexingDaemonListener(IndexingDaemonListener listener){ if(listener != null){ return listeners.add(listener); } else { return false; } } public boolean removeIndexingDaemonListener(IndexingDaemonListener listener){ if(listener != null){ return listeners.remove(listener); } else { return false; } } public void fireIndexingDaemonEvent(){ Set<IndexingDaemonListener> copy; synchronized (listeners) { copy = new HashSet<IndexingDaemonListener>(listeners); } IndexingDaemonEventObject eventObject = new IndexingDaemonEventObject(this); for(IndexingDaemonListener listener : copy){ listener.indexingDaemonFinished(eventObject); } } /** * Currently only used to notify listener that this Daemon has processed * all entities * TODO: I would like to use generics here, but I was not able to figure out * how to used them in a way, that one can still register an Listener that * uses <code>IndexingDaemonListener<? super CI,? super></code> with * the {@link IndexingDaemon#addIndexingDaemonListener(IndexingDaemonListener)} * and {@link IndexingDaemon#removeIndexingDaemonListener(IndexingDaemonListener)} * methods. * @author Rupert Westenthaler * */ public static interface IndexingDaemonListener { void indexingDaemonFinished(IndexingDaemonEventObject indexingDaemonEventObject); } public static class IndexingDaemonEventObject extends EventObject { private static final long serialVersionUID = -1L; public IndexingDaemonEventObject(IndexingDaemon<?,?> indexingDaemon){ super(indexingDaemon); } @Override public IndexingDaemon<?,?> getSource() { return (IndexingDaemon<?,?>)super.getSource(); } } protected final BlockingQueue<QueueItem<CI>> getConsumeQueue() { return consume; } protected final BlockingQueue<QueueItem<PI>> getProduceQueue() { return produce; } public String getName() { return this.name; } /** * The order of this Daemon. Guaranteed to be NOT NULL * @return the order */ public final Integer getSequence() { return sequence; } @Override public int compareTo(IndexingDaemon<?,?> o) { int compare = sequence.compareTo(o.sequence); if(compare != 0){ return compare; } else { //the ordering within the same sequence position is of no importance //but it is important to only return 0 if the two Objects are //equals because we will use this class together with SortedSets! if(hashCode() == o.hashCode()){ if(equals(o)){ return 0; } else { return -1; //no idea if that is OK } } else { return hashCode()-o.hashCode(); } } } }