/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2009-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.provision; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.opennms.core.concurrent.PausibleScheduledThreadPoolExecutor; /** * This class takes the work out of scheduling and queuing calls from the provisioner. Each provisioning * adapter can extend this class for this functionality. The interface API methods are final so that * the child class can not implement them and override the queuing for what would be the point. (Unless * of course we decide that it is worth while to override a subset of the API methods. * * To use the class, have your provisioning adapter extend this abstract class. You see that you must * implement abstract methods to compile. * * To change the schedule, override the createScheduleForNode method and return a schedule suitable for * the node. In this base class, the same schedule is used for all nodes. * * TODO: Add logging * TODO: Verify correct Exception handling * TODO: Write tests (especially the equals method of the NodeOperation for proper queue handling) * * @author <a href="mailto:david@opennms.org">David Hustace</a> * @version $Id: $ */ public abstract class SimpleQueuedProvisioningAdapter2 implements ProvisioningAdapter { private volatile PausibleScheduledThreadPoolExecutor m_executorService; /** * <p>Constructor for SimpleQueuedProvisioningAdapter2.</p> * * @param executorService a {@link org.opennms.core.concurrent.PausibleScheduledThreadPoolExecutor} object. */ protected SimpleQueuedProvisioningAdapter2(PausibleScheduledThreadPoolExecutor executorService) { m_executorService = executorService; } /** * <p>Constructor for SimpleQueuedProvisioningAdapter2.</p> */ protected SimpleQueuedProvisioningAdapter2() { this(createDefaultSchedulerService()); } private static PausibleScheduledThreadPoolExecutor createDefaultSchedulerService() { PausibleScheduledThreadPoolExecutor executorService = new PausibleScheduledThreadPoolExecutor(1); return executorService; } /* * (non-Javadoc) * @see org.opennms.netmgt.provision.ProvisioningAdapter#getName() */ /** * <p>getName</p> * * @return a {@link java.lang.String} object. */ public abstract String getName(); /** * This method is called when the scheduled * Adapters extending this class must implement this method * * @param nodeId a int. * @return a boolean. */ public abstract boolean isNodeReady(int nodeId); /** * <p>processPendingOperationForNode</p> * * @param op a {@link org.opennms.netmgt.provision.SimpleQueuedProvisioningAdapter2.AdapterOperation} object. * @throws org.opennms.netmgt.provision.ProvisioningAdapterException if any. */ public abstract void processPendingOperationForNode(AdapterOperation op) throws ProvisioningAdapterException; /** * Override this method to change the default schedule * @param adapterOperationType * @return */ AdapterOperationSchedule createScheduleForNode(int nodeId, AdapterOperationType adapterOperationType) { return new AdapterOperationSchedule(); } /* * (non-Javadoc) * @see org.opennms.netmgt.provision.ProvisioningAdapter#addNode(int) */ /** {@inheritDoc} */ public final void addNode(int nodeId) { AdapterOperation op = new AdapterOperation(Integer.valueOf(nodeId), AdapterOperationType.ADD, createScheduleForNode(nodeId, AdapterOperationType.ADD)); synchronized (m_executorService) { if (!m_executorService.getQueue().contains(op)) { op.schedule(m_executorService); } } } /* * (non-Javadoc) * @see org.opennms.netmgt.provision.ProvisioningAdapter#updateNode(int) */ /** {@inheritDoc} */ public final void updateNode(int nodeId) { AdapterOperation op = new AdapterOperation(Integer.valueOf(nodeId), AdapterOperationType.UPDATE, createScheduleForNode(nodeId, AdapterOperationType.UPDATE)); synchronized (m_executorService) { if (!m_executorService.getQueue().contains(op)) { op.schedule(m_executorService); } } } /* * (non-Javadoc) * @see org.opennms.netmgt.provision.ProvisioningAdapter#deleteNode(int) */ /** {@inheritDoc} */ public final void deleteNode(int nodeId) { AdapterOperation op = new AdapterOperation(Integer.valueOf(nodeId), AdapterOperationType.DELETE, createScheduleForNode(nodeId, AdapterOperationType.DELETE)); synchronized (m_executorService) { if (!m_executorService.getQueue().contains(op)) { op.schedule(m_executorService); } } } /* * (non-Javadoc) * @see org.opennms.netmgt.provision.ProvisioningAdapter#nodeConfigChanged(int) */ /** {@inheritDoc} */ public final void nodeConfigChanged(int nodeId) { AdapterOperation op = new AdapterOperation(Integer.valueOf(nodeId), AdapterOperationType.CONFIG_CHANGE, createScheduleForNode(nodeId, AdapterOperationType.CONFIG_CHANGE)); synchronized (m_executorService) { if (!m_executorService.getQueue().contains(op)) { op.schedule(m_executorService); } } } /** * Represents a node operation to be queued and scheduled. * * @author <a href="mailto:david@opennms.org">David Hustace</a> * */ class AdapterOperation implements Runnable { private final Integer m_nodeId; private final AdapterOperationType m_type; private AdapterOperationSchedule m_schedule; private final Date m_createTime; public AdapterOperation(Integer nodeId, AdapterOperationType type, AdapterOperationSchedule schedule) { m_nodeId = nodeId; m_type = type; m_schedule = schedule; m_createTime = new Date(); } public Integer getNodeId() { return m_nodeId; } public Date getCreateTime() { return m_createTime; } public AdapterOperationType getType() { return m_type; } public AdapterOperationSchedule getSchedule() { return m_schedule; } ScheduledFuture<?> schedule(ScheduledExecutorService executor) { ScheduledFuture<?> future = executor.scheduleWithFixedDelay(this, m_schedule.m_initalDelay, m_schedule.m_interval, m_schedule.m_unit); return future; } //TODO: Test this behavior with Unit Tests, for sure! @Override public boolean equals(Object that) { boolean equals = false; if (this == that) { equals = true; } if (that == null) { throw new IllegalArgumentException("the Operation Object passed is either null or of the wrong class"); } if (this.m_nodeId == ((AdapterOperation)that).getNodeId() && this.m_type == ((AdapterOperation)that).getType()) { equals = true; } return equals; } @Override public String toString() { return "Operation: "+m_type+" on Node: "+m_nodeId; } public void run() { if (isNodeReady(m_nodeId)) { processPendingOperationForNode(this); } if (isNodeReady(m_nodeId)) { synchronized (m_executorService) { processPendingOperationForNode(this); } } } } /** * Simple class for handling the scheduling bits for an AdapterOperation * * @author <a href="mailto:david@opennms.org">David Hustace</a> */ static class AdapterOperationSchedule { long m_initalDelay; long m_interval; TimeUnit m_unit; public AdapterOperationSchedule(long initalDelay, long interval, TimeUnit unit) { m_initalDelay = initalDelay; m_interval = interval; m_unit = unit; } public AdapterOperationSchedule() { this(60, 60, TimeUnit.SECONDS); } public long getInitalDelay() { return m_initalDelay; } public long getInterval() { return m_interval; } public TimeUnit getUnit() { return m_unit; } } /** * Since the operations are queued, we need a way of identifying type of provisioning action * happened to create the operation. The adapters will need to know what is the appropriate * action to take. * * @author <a href="mailto:david@opennms.org">David Hustace</a> */ static enum AdapterOperationType { ADD(1, "Add"), UPDATE(2, "Update"), DELETE(3, "Delete"), CONFIG_CHANGE(4, "Configuration Change"); private static final Map<Integer, AdapterOperationType> m_idMap; private static final List<Integer> m_ids; private int m_id; private String m_label; static { m_ids = new ArrayList<Integer>(values().length); m_idMap = new HashMap<Integer, AdapterOperationType>(values().length); for (AdapterOperationType operation : values()) { m_ids.add(operation.getId()); m_idMap.put(operation.getId(), operation); } } private AdapterOperationType(int id, String label) { m_id = id; m_label = label; } private Integer getId() { return m_id; } @Override public String toString() { return m_label; } public static AdapterOperationType get(int id) { if (m_idMap.containsKey(id)) { return m_idMap.get(id); } else { throw new IllegalArgumentException("Cannot create AdapterOperation from unknown ID " + id); } } } /** * <p>getExecutorService</p> * * @return a {@link org.opennms.core.concurrent.PausibleScheduledThreadPoolExecutor} object. */ public PausibleScheduledThreadPoolExecutor getExecutorService() { return m_executorService; } }