/* * 2012-3 Red Hat Inc. and/or its affiliates and other contributors. * * 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 org.overlord.rtgov.active.collection; import java.text.MessageFormat; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.mvel2.MVEL; import org.overlord.rtgov.active.collection.predicate.Predicate; /** * This class defines an Active Collection Source that is * responsible for retrieving the data (with optional pre- * processing) to be placed within an associated active * collection, and then maintaining that information with * subsequent updates and eventual removal. * */ @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class") public class ActiveCollectionSource { private static final Logger LOG=Logger.getLogger(ActiveCollectionSource.class.getName()); private String _name=null; private ActiveCollectionType _type=ActiveCollectionType.List; private ActiveCollectionVisibility _visibility=ActiveCollectionVisibility.Public; private ActiveCollectionFactory _factory=null; private boolean _lazy=false; private long _itemExpiration=0; private int _maxItems=0; private int _highWaterMark=0; private java.util.List<DerivedDefinition> _derived= new java.util.ArrayList<DerivedDefinition>(); private ActiveCollection _activeCollection=null; private java.util.List<ActiveCollection> _derivedActiveCollections= new java.util.ArrayList<ActiveCollection>(); private java.util.List<AbstractActiveChangeListener> _listeners= new java.util.ArrayList<AbstractActiveChangeListener>(); private String _maintenanceScript=null; private java.io.Serializable _maintenanceScriptExpression=null; private String _scheduledScript=null; private java.io.Serializable _scheduledScriptExpression=null; private long _scheduledInterval=0; private java.util.Timer _scheduledTimer=null; private java.util.Map<String,Object> _variables=new java.util.HashMap<String, Object>(); private java.util.Map<String,Object> _properties=new java.util.HashMap<String, Object>(); private long _aggregationDuration=0; private String _groupBy=null; private java.io.Serializable _groupByExpression=null; private String _aggregationScript=null; private java.io.Serializable _aggregationScriptCompiled=null; private java.util.Map<Object, java.util.List<Object>> _groupedEvents= new java.util.HashMap<Object, java.util.List<Object>>(); private Aggregator _aggregator=null; private boolean _preinitialized=false; private ActiveCollectionContext _context=null; /** * The default constructor. */ public ActiveCollectionSource() { } /** * The copy constructor. * * @param source The source to copy */ public ActiveCollectionSource(ActiveCollectionSource source) { _name = source._name; _type = source._type; _visibility = source._visibility; _factory = source._factory; _lazy = source._lazy; _itemExpiration = source._itemExpiration; _maxItems = source._maxItems; _highWaterMark = source._highWaterMark; _activeCollection = source._activeCollection; _listeners.addAll(source._listeners); _maintenanceScript = source._maintenanceScript; _maintenanceScriptExpression = source._maintenanceScriptExpression; _scheduledScript = source._scheduledScript; _scheduledScriptExpression = source._scheduledScriptExpression; _scheduledInterval = source._scheduledInterval; _scheduledTimer = source._scheduledTimer; _variables = new java.util.HashMap<String, Object>(source._variables); _properties = new java.util.HashMap<String, Object>(source._properties); _aggregationDuration = source._aggregationDuration; _groupBy = source._groupBy; _groupByExpression = source._groupByExpression; _aggregationScript = source._aggregationScript; _aggregationScriptCompiled = source._aggregationScriptCompiled; _groupedEvents = new java.util.HashMap<Object,java.util.List<Object>>(source._groupedEvents); _aggregator = source._aggregator; _preinitialized = source._preinitialized; } /** * This method sets the name of the active collection that * this source represents. * * @param name The active collection name */ public void setName(String name) { _name = name; } /** * This method returns the name of the active collection associated * with this source. * * @return The name */ public String getName() { return (_name); } /** * This method returns the active collection type associated * with the source. * * @return The type */ public ActiveCollectionType getType() { return (_type); } /** * This method sets the active collection type. * * @param type The type */ public void setType(ActiveCollectionType type) { _type = type; } /** * This method returns the active collection visibility associated * with the source. * * @return The visibility */ public ActiveCollectionVisibility getVisibility() { return (_visibility); } /** * This method sets the active collection visibility. * * @param visibility The visibility */ public void setVisibility(ActiveCollectionVisibility visibility) { _visibility = visibility; } /** * This method returns the factory responsible for * creating the active collection. * * @return The factory */ public ActiveCollectionFactory getFactory() { return (_factory); } /** * This method sets the factory responsible for * creating the active collection. * * @param factory The factory */ public void setFactory(ActiveCollectionFactory factory) { _factory = factory; } /** * This method returns whether the active collection associated * with this source should be created lazily or upon registration. * * @return Whether to create active collection lazily (default is false) */ public boolean getLazy() { return (_lazy); } /** * This method sets whether the active collection associated * with this source should be created lazily or upon registration. * * @param lazy Whether to create active collection lazily */ public void setLazy(boolean lazy) { _lazy = lazy; } /** * This method returns the item expiration duration. * * @return The number of milliseconds that the item should remain * in the active collection, or 0 if not relevant */ public long getItemExpiration() { return (_itemExpiration); } /** * This method sets the item expiration duration. * * @param expire The item expiration duration, or zero * for no expiration duration */ public void setItemExpiration(long expire) { _itemExpiration = expire; } /** * This method returns the maximum number of items that should be * contained within the active collection. The default policy will * be to remove oldest entry when maximum number is reached. * * @return The maximum number of items, or 0 if not relevant */ public int getMaxItems() { return (_maxItems); } /** * This method sets the maximum number of items * that will be in the active collection. * * @param max The maximum number of items, or zero * for no limit */ public void setMaxItems(int max) { _maxItems = max; } /** * This method gets the high water mark, used to indicate * when a warning should be issued. * * @return The high water mark, or 0 if not relevant */ public int getHighWaterMark() { return (_highWaterMark); } /** * This method sets the high water mark, used to indicate * when a warning should be issued. * * @param highWaterMark The high water mark */ public void setHighWaterMark(int highWaterMark) { _highWaterMark = highWaterMark; } /** * This method returns the list of derived active collection * definitions. * * @return The derived active collection definitions */ public java.util.List<DerivedDefinition> getDerived() { return (_derived); } /** * This method sets the list of derived active collection * definitions. * * @param derived The derived active collection definitions */ public void setDerived(java.util.List<DerivedDefinition> derived) { _derived = derived; } /** * This method determines whether the source has an associated * active collection. * * @return Whether an active collection has been created for the source */ public boolean hasActiveCollection() { return (_activeCollection != null); } /** * This method returns the Active Collection associated with the * source. * * @return The active collection */ @JsonIgnore public synchronized ActiveCollection getActiveCollection() { // Create the active collection, if not already defined if (_activeCollection == null) { ActiveCollectionFactory factory=(_factory == null ? ActiveCollectionFactory.DEFAULT_FACTORY : _factory); _activeCollection = factory.createActiveCollection(this); // If active change listeners defined, then // add them to the active collection if (_activeCollection != null && _listeners.size() > 0) { for (AbstractActiveChangeListener l : _listeners) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Register active collection '" +getName()+"' with listener from source: "+l); } _activeCollection.addActiveChangeListener(l); } } } return (_activeCollection); } /** * This method sets the Active Collection associated with the * source. * * @param ac The active collection */ public void setActiveCollection(ActiveCollection ac) { _activeCollection = ac; } /** * This method returns the derived Active Collections associated with the * source. * * @return The derived active collections */ @JsonIgnore public synchronized java.util.List<ActiveCollection> getDerivedActiveCollections() { // Create the derived active collections, if not already defined if (_activeCollection != null && _derived.size() > 0 && _derivedActiveCollections.size() == 0) { // Create derived active collections for (DerivedDefinition dd : getDerived()) { ActiveCollection derived=_activeCollection.derive(dd.getName(), _context, dd.getPredicate(), dd.getProperties()); _derivedActiveCollections.add(derived); } } return (_derivedActiveCollections); } /** * This method returns the list of active change listeners to be * automatically registered against the active collection associated * with this source.. * * @return The list of active change listeners */ public java.util.List<AbstractActiveChangeListener> getActiveChangeListeners() { return (_listeners); } /** * This method returns the list of active change listeners to be * automatically registered against the active collection associated * with this source.. * * @param listeners The list of active change listeners */ public void setActiveChangeListeners(java.util.List<AbstractActiveChangeListener> listeners) { _listeners = listeners; } /** * This method sets the maintenance script. * * @param script The maintenance script */ public void setMaintenanceScript(String script) { _maintenanceScript = script; } /** * This method gets the maintenance script. * * @return The maintenance script */ public String getMaintenanceScript() { return (_maintenanceScript); } /** * This method sets the scheduled script. * * @param script The scheduled script */ public void setScheduledScript(String script) { _scheduledScript = script; } /** * This method gets the scheduled script. * * @return The scheduled script */ public String getScheduledScript() { return (_scheduledScript); } /** * This method sets the scheduled interval. * * @param interval The scheduled interval */ public void setScheduledInterval(long interval) { _scheduledInterval = interval; } /** * This method gets the scheduled interval. * * @return The scheduled interval */ public long getScheduledInterval() { return (_scheduledInterval); } /** * This method returns the interval variables that * can be used by scripts to cache information used * between invocations. * * @return The internal variables */ protected java.util.Map<String,Object> getVariables() { return (_variables); } /** * This method returns the properties. * * @return The properties */ public java.util.Map<String,Object> getProperties() { return (_properties); } /** * This method sets the properties. * * @param props The properties */ public void setProperties(java.util.Map<String,Object> props) { _properties = props; } /** * This method sets the aggregation duration. * * @param duration The aggregation duration */ public void setAggregationDuration(long duration) { _aggregationDuration = duration; } /** * This method gets the aggregation duration. * * @return The aggregation duration */ public long getAggregationDuration() { return (_aggregationDuration); } /** * This method sets the 'group by' expression. * * @param expr The expression */ public void setGroupBy(String expr) { _groupBy = expr; } /** * This method gets the 'group by' expression. * * @return The expression */ public String getGroupBy() { return (_groupBy); } /** * This method sets the aggregation script. * * @param script The aggregation script */ public void setAggregationScript(String script) { _aggregationScript = script; } /** * This method gets the aggregation script. * * @return The aggregation script */ public String getAggregationScript() { return (_aggregationScript); } /** * This method pre-initializes the active collection source * in situations where it needs to be initialized before * registration with the manager. This may be required * where the registration is performed in a different * contextual classloader than the source was loaded. * * @throws Exception Failed to pre-initialize */ protected void preInit() throws Exception { if (!_preinitialized) { _preinitialized = true; if (LOG.isLoggable(Level.FINE)) { LOG.fine("Pre-Initializing script="+_aggregationScript +" compiled="+_aggregationScriptCompiled); } // Only initialize if the script is specified, but not yet compiled if (_aggregationScript != null && _aggregationScriptCompiled == null) { java.io.InputStream is=Thread.currentThread().getContextClassLoader().getResourceAsStream(_aggregationScript); if (is == null) { LOG.severe(MessageFormat.format( java.util.PropertyResourceBundle.getBundle( "active-collection.Messages").getString("ACTIVE-COLLECTION-1"), _aggregationScript)); } else { byte[] b=new byte[is.available()]; is.read(b); is.close(); // Compile expression _aggregationScriptCompiled = MVEL.compileExpression(new String(b)); } } if (LOG.isLoggable(Level.FINE)) { LOG.fine("Pre-Initializing maintenance script="+_maintenanceScript +" compiled="+_maintenanceScriptExpression); } // Only initialize if the script is specified, but not yet compiled if (_maintenanceScript != null && _maintenanceScriptExpression == null) { java.io.InputStream is=Thread.currentThread().getContextClassLoader().getResourceAsStream(_maintenanceScript); if (is == null) { LOG.severe(MessageFormat.format( java.util.PropertyResourceBundle.getBundle( "active-collection.Messages").getString("ACTIVE-COLLECTION-1"), _maintenanceScript)); } else { byte[] b=new byte[is.available()]; is.read(b); is.close(); // Compile expression _maintenanceScriptExpression = MVEL.compileExpression(new String(b)); } } if (LOG.isLoggable(Level.FINE)) { LOG.fine("Pre-Initializing scheduled script="+_scheduledScript +" compiled="+_scheduledScriptExpression); } // Only initialize if the script is specified, but not yet compiled if (_scheduledScript != null && _scheduledScriptExpression == null) { java.io.InputStream is=Thread.currentThread().getContextClassLoader().getResourceAsStream(_scheduledScript); if (is == null) { LOG.severe(MessageFormat.format( java.util.PropertyResourceBundle.getBundle( "active-collection.Messages").getString("ACTIVE-COLLECTION-1"), _scheduledScript)); } else { byte[] b=new byte[is.available()]; is.read(b); is.close(); // Compile expression _scheduledScriptExpression = MVEL.compileExpression(new String(b)); } } // If active change listeners defined, then pre-init them if (_listeners.size() > 0) { for (AbstractActiveChangeListener l : _listeners) { l.preInit(); } } } } /** * This method initializes the active collection source. * * @param context The context * @throws Exception Failed to initialize source */ public void init(ActiveCollectionContext context) throws Exception { _context = context; preInit(); // If active change listeners defined, then initialize them if (_listeners.size() > 0) { for (AbstractActiveChangeListener l : _listeners) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("Initialize active collection '" +getName()+"' with listener from source: "+l); } l.init(); if (_activeCollection != null) { _activeCollection.addActiveChangeListener(l); } } } if (_groupBy != null) { // Compile expression _groupByExpression = MVEL.compileExpression(_groupBy); if (_aggregationDuration > 0) { // Create aggregator _aggregator = new Aggregator(); } } // Check if scheduled timer should be started if (_scheduledScriptExpression != null && _scheduledInterval > 0) { _scheduledTimer = new java.util.Timer(); _scheduledTimer.scheduleAtFixedRate(new TimerTask() { public void run() { java.util.Map<String,Object> vars= new java.util.HashMap<String, Object>(); vars.put("acs", ActiveCollectionSource.this); vars.put("variables", _variables); if (LOG.isLoggable(Level.FINE)) { LOG.fine("Call scheduled script on '"+getName() +"' with variables: "+_variables); } synchronized (ActiveCollectionSource.this) { MVEL.executeExpression(_scheduledScriptExpression, vars); } } }, 0, _scheduledInterval); } } /** * This method is invoked to handle the supplied entry details. * If a maintenance script has been defined, then it will be used * to manage the entry, otherwise it will be inserted * into the associated collection. * * @param key The key * @param value The value */ public void maintainEntry(Object key, Object value) { if (_maintenanceScriptExpression != null) { java.util.Map<String,Object> vars= new java.util.HashMap<String, Object>(); vars.put("acs", this); vars.put("key", key); vars.put("value", value); vars.put("variables", _variables); synchronized (this) { MVEL.executeExpression(_maintenanceScriptExpression, vars); } } else { insert(key, value); } } /** * This method adds the supplied object to the active collection. * If the optional key is provided, it can either be an index * if inserting into a particular position in a list (otherwise * default is to add to the end of the list), or a specific value * intended to be the key for a map. * * @param key The optional key * @param value The value */ public void insert(Object key, Object value) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("insert key="+key+" value="+value+" ac="+getActiveCollection()); } getActiveCollection().doInsert(key, value); } /** * This method updates the supplied value within the active collection, * based on the supplied key. If the active collection is a list, then * the key will be an integer reflecting the index of the element being * updated. If the active collection is a map, then the key will be * associated with the element to be updated. * * @param key The key * @param value The value */ public void update(Object key, Object value) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("update key="+key+" value="+value+" ac="+getActiveCollection()); } getActiveCollection().doUpdate(key, value); } /** * This method removes the supplied object from the active collection. * * @param key The optional key, not required for lists * @param value The value */ public void remove(Object key, Object value) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("remove key="+key+" value="+value+" ac="+getActiveCollection()); } getActiveCollection().doRemove(key, value); } /** * This method returns the list of map of grouped events by key. * * @return The grouped events */ @JsonIgnore protected java.util.Map<Object, java.util.List<Object>> getGroupedEvents() { return (_groupedEvents); } /** * This method processes the supplied event to determine its group * for subsequent aggregation. * * @param event The event to be processed */ protected void aggregateEvent(java.io.Serializable event) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("aggregateEvent event="+event); } synchronized (_groupedEvents) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Aggregating event: "+event); } // Derive key Object key=MVEL.executeExpression(_groupByExpression, event); if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Derived key '"+key+"' for event: "+event); } if (key == null) { LOG.severe(MessageFormat.format( java.util.PropertyResourceBundle.getBundle( "active-collection.Messages").getString("ACTIVE-COLLECTION-4"), _groupBy, event)); } else { java.util.List<Object> list=_groupedEvents.get(key); if (list == null) { list = new java.util.ArrayList<Object>(); _groupedEvents.put(key, list); } list.add(event); } } } /** * This method publishes any aggregated events to the associated active * collection. */ protected void publishAggregateEvents() { java.util.Map<Object, java.util.List<Object>> source=null; // Take a copy of the events and clear the original map synchronized (_groupedEvents) { if (_groupedEvents.size() > 0) { source = new java.util.HashMap<Object, java.util.List<Object>>(_groupedEvents); _groupedEvents.clear(); } } if (source != null) { if (_aggregationScriptCompiled != null) { java.util.Map<String,java.util.List<Object>> vars= new java.util.HashMap<String, java.util.List<Object>>(); for (java.util.List<Object> list : source.values()) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("publishAggregateEvents list="+list); } vars.clear(); vars.put("events", list); Object result= MVEL.executeExpression(_aggregationScriptCompiled, vars); if (result == null) { LOG.severe(java.util.PropertyResourceBundle.getBundle( "active-collection.Messages").getString("ACTIVE-COLLECTION-5")); if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Script="+_aggregationScript); LOG.finest("List of Events="+list); } } else { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("publishAggregateEvents result="+result); } maintainEntry(null, result); } } } else { LOG.severe(MessageFormat.format( java.util.PropertyResourceBundle.getBundle( "active-collection.Messages").getString("ACTIVE-COLLECTION-6"), source)); } } } /** * This method closes the active collection source. * * @throws Exception Failed to close source */ public void close() throws Exception { // Unregister any pre-defined listeners if (_listeners.size() > 0 && _activeCollection != null) { for (AbstractActiveChangeListener l : _listeners) { _activeCollection.removeActiveChangeListener(l); l.close(); } } if (_aggregator != null) { _aggregator.cancel(); } if (_scheduledTimer != null) { _scheduledTimer.cancel(); } } /** * This class provides the definition of a derived * active collection that will be associated with * the main collection from this source. * */ public static class DerivedDefinition { private String _name=null; private Predicate _predicate=null; private java.util.Map<String,Object> _properties=new java.util.HashMap<String, Object>(); /** * The default constructor. */ public DerivedDefinition() { } /** * This method sets the name of the derived collection. * * @param name The name */ public void setName(String name) { _name = name; } /** * This method gets the name of the derived collection. * * @return The name */ public String getName() { return (_name); } /** * This method sets the predicate for the derived collection. * * @param predicate The predicate */ public void setPredicate(Predicate predicate) { _predicate = predicate; } /** * This method gets the predicate for the derived collection. * * @return The predicate */ public Predicate getPredicate() { return (_predicate); } /** * This method returns the properties. * * @return The properties */ public java.util.Map<String,Object> getProperties() { return (_properties); } /** * This method sets the properties. * * @param props The properties */ public void setProperties(java.util.Map<String,Object> props) { _properties = props; } } /** * This class implements the aggregation functionality triggered * at configured intervals. * */ public class Aggregator extends java.util.TimerTask { private java.util.Timer _timer=new java.util.Timer(); /** * This is the constructor. */ public Aggregator() { _timer.scheduleAtFixedRate(this, _aggregationDuration, _aggregationDuration); } /** * {@inheritDoc} */ @Override public void run() { publishAggregateEvents(); } } }