blob: 81b98fd5c8bcd5e06c075d695a4afcff88714e22 [file] [log] [blame]
/*
* Copyright 1996-2011 Niclas Hedhman.
*
* 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.qi4j.library.alarm;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import org.qi4j.api.common.Optional;
import org.qi4j.api.common.UseDefaults;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.This;
import org.qi4j.api.property.Property;
/**
* Defines the basic AlarmPoint interface.
* <p>
* This is the basic interface for the whole AlarmPoint System. The AlarmPoint
* is created by calling <code>createAlarm()</code> method in the
* <code>AlarmSystem</code> or the <code>AlarmModel</code>.
* </p>
* <ul>
* <li>All alarms carries a set of attributes, runtime extendable.</li>
* <li>All alarms can be activated (on), deactivated (off) and acknowledged.</li>
* <li>All alarms have an AlarmStatus.</li>
* <li>All alarms generates AlarmEvents.</li>
* <li>The behaviour of the AlarmPoint is defined by an AlarmModel.</li>
* <li>Every AlarmPoint can have its own AlarmModel.</li>
* <li>Any number of AlarmStates and AlarmEvents can be defined in an AlarmModel.</li>
* </ul>
* <p>
* Alarms can be triggered by an standard trigger, which are java.lang.Strings.
* 3 triggers are pre-defined; <i>activate</i>,<i>deactivate</i> and
* <i>acknowledge</i> and must be present in all standard systems and standard impl.
* </p>
* <p>
* The basic usage looks like this;
* <pre><code>
* // Creation
* AlarmPoint ala1 = alarmService.createAlarm( "My AlarmPoint" );
* :
* :
* // use
* if( alarmcondition ) // The condition should only detect transitions.
* ala1.trigger( this, "activate" );
* </code></pre>
* <p>
* It is important to know that every call to <code>trigger()</code>
* will generate an AlarmEvent, so the <code>trigger()</code> should
* only be called when the standard condition changes. For this purpose, there is
* a convenience method, that will create/generate the <code>trigger</code>
* method calls when a boolean standard condition changes. The usage is fairly
* simple.
* </p>
* <p>
* Example;
* </p>
* <pre><code>
* ala1.updateCondition( value &gt; highlimit );
* </code></pre>
* <p>
* It is possible to mix and match the usage of <code>updateCondition()</code>
* and <code>trigger()</code> methods without any concerns.
* </p>
* <p>
* To create alarms with different AlarmModels, other than the
* default as shown above, you need to retrieve the AlarmModel that
* fulfill the needs required. This can be done in the following manner;
* </p>
* <pre><code>
* AlarmModel[] impl = alarmService.getAlarmModelsAvailable();
* // selection algorithm
* AlarmPoint ala2 = impl[selected].createAlarm( "My AlarmPoint" );
* </code></pre>
* <p>
* The default AlarmModel can be changed by a call to the
* <code>AlarmSystem.setDefaultAlarmModel()</code> and
* ALL ALARMS that has the old AlarmModel assigned to it, will be
* transferred to the new default AlarmModel. It is important to
* understand that this is done irregardless of whether the AlarmPoint was
* created from the <code>AlarmSystem.createAlarm()</code> method or
* the <code>AlarmModel.createAlarm()</code> method. If distinct different
* behaviours are required for certain Alarms, and yet want to allow
* users to freely select AlarmModel for all other Alarms, one need
* to create two instances of the same AlarmModels, one used solely
* for the pre-defined AlarmPoint behaviours, and the others for the rest of
* the Alarms.
* </p>
*/
public interface AlarmPoint
{
String STATUS_NORMAL = "Normal";
String STATUS_ACTIVATED = "Activated";
String STATUS_DEACTIVATED = "Deactivated";
String STATUS_REACTIVATED = "Reactivated";
String STATUS_ACKNOWLEDGED = "Acknowledged";
String STATUS_DISABLED = "Disabled";
String STATUS_BLOCKED = "Blocked";
String EVENT_ENABLING = "enabled";
String EVENT_DISABLING = "disabled";
String EVENT_BLOCKING = "blocked";
String EVENT_UNBLOCKING = "unblocked";
String EVENT_ACTIVATION = "activation";
String EVENT_DEACTIVATION = "deactivation";
String EVENT_ACKNOWLEDGEMENT = "acknowledgement";
String TRIGGER_ACTIVATE = "activate";
String TRIGGER_DEACTIVATE = "deactivate";
String TRIGGER_ACKNOWLEDGE = "acknowledge";
String TRIGGER_BLOCK = "block";
String TRIGGER_UNBLOCK = "unblock";
String TRIGGER_ENABLE = "enable";
String TRIGGER_DISABLE = "disable";
/**
* Trigger a state change.
* <p>
* When the AlarmPoint object receives a trigger, it must consult the
* AlarmModel and figure out if there is an actual state change
* occurring and if any AlarmEvents should be fired.
* </p>
*
* @param trigger The trigger to execute if existing in the AlarmModel.
*
* @throws IllegalArgumentException if a trigger is not a known one.
*/
void trigger( String trigger )
throws IllegalArgumentException;
/**
* Activates an AlarmPoint.
* <p>
* Convenience method for:
* </p>
* <pre><code>
* trigger( "activate" );
* </code>
* </pre>
*/
void activate();
/**
* Deactivates an AlarmPoint.
* Convinience method for:<pre>
* trigger( "deactivate" );
* </pre>
*/
void deactivate();
/**
* Acknowledges an AlarmPoint.
* Convinience method for:<pre>
* trigger( source, "acknowledge" );
* </pre>
*/
void acknowledge();
/**
* Get AlarmPoint condition.
* To reduce AlarmPoint condition calculations for Implementors, each AlarmPoint should
* be able to work with a "true/false" trigger. Only changes to this trigger
* will cause an event.
*
* @return The condition of the AlarmPoint, which is used to simplify trigging of activate and deactivate.
*/
boolean currentCondition();
/**
* Set AlarmPoint condition.
* To reduce AlarmPoint condition calculations for Implementors, each AlarmPoint should
* be able to work with a "true/false" trigger. Only changes to this trigger
* will cause an event.
* Causes an Activation or Deactivation if state of condition changes.
*
* @param condition Sets the AlarmPoint condition.
*/
void updateCondition( boolean condition );
/**
* Returns the current state of the standard.
*
* @return The AlarmStatus (interface) object
*/
AlarmStatus currentStatus();
/**
* Returns the AlarmHistory of the standard.
*
* @return The AlarmHistory object, or null if AlarmHistory is not supported.
*/
AlarmHistory history();
/**
* Return all attribute names
*
* @return the names of the attributes of this AlarmPoint.
*/
List<String> attributeNames();
/**
* Return the attribute of the given name.
*
* @param name The name of the attribute to return.
*
* @return the named attribute of this AlarmPoint.
*/
String attribute( String name );
/**
* Sets the attribute of the given name.
*
* @param name The name of the attribute to set.
* @param value The value to set the named attribute to.
*/
void setAttribute( String name, @Optional String value );
/**
* Returns the Name of the AlarmPoint.
* This normally returns the human readable technical name of the AlarmPoint.
*
* @return the name of the AlarmPoint.
*/
String name();
/**
* Returns a Description of the AlarmPoint.
* This normally returns a full Description of the AlarmPoint in the
* default Locale.
*
* @return a human-readable description of the AlarmPoint in the default locale.
*/
String descriptionInDefaultLocale();
/**
* Returns a Description of the AlarmPoint.
* This normally returns a full Description of the AlarmPoint in the
* Locale. If Locale is <code><b>null</b></code>, then the
* default Locale is used.
*
* @param locale The locale to return the description in, or null to use default locale.
*
* @return a human-readable description of the AlarmPoint in the given locale.
*/
String description( Locale locale );
/**
* The {@link AlarmClass} of the AlarmPoint.
*
* The {@link AlarmClass} indicates the urgency of {@link AlarmEvent}s emitted from the AlarmPoint. The property
* has {@link UseDefaults} annotation so that the application developer can set the default during assembly.
* The default {@link org.qi4j.bootstrap.Assembler} in this library defaults alarms to {@link AlarmClass} <b>B</b>.
*
* @return the property instance that contains the {@link AlarmClass}.
*/
@UseDefaults
Property<AlarmClass> alarmClass();
/**
* The {@link AlarmCategory} of this AlarmPoint.
*
* AlarmCategory is used to group Alarms together, which can be used to forward {@link AlarmEvent} to different
* destinations, produce reports for different target audiences or separation of aggregation.
*
* @return the property instance that contains the {@link AlarmCategory}.
*/
Property<AlarmCategory> category();
/**
* The AlarmState is an internal type, used inside the AlarmPoint for all state that needs to be persisted on disk
* and/or transferred across networks.
*/
interface AlarmState
{
@AlarmNameFormat
Property<String> systemName();
@Optional
Property<String> description();
@UseDefaults
Property<Map<String, String>> attributes();
Property<AlarmStatus> currentStatus();
}
abstract class AlarmPointMixin
implements AlarmPoint
{
@Service
private AlarmModel model;
@Service
private AlarmSystem alarmSystem;
@This
private AlarmPoint me;
@This
private AlarmState state;
@This
private AlarmHistory history;
@Override
public void setAttribute( String name, String value )
{
Map<String, String> properties = state.attributes().get();
if( value == null )
{
properties.remove( name );
}
else
{
properties.put( name, value );
}
state.attributes().set( properties );
}
@Override
public String attribute( String name )
{
return state.attributes().get().get( name );
}
@Override
public List<String> attributeNames()
{
ArrayList<String> result = new ArrayList<String>();
for( String name : state.attributes().get().keySet() )
{
result.add( name );
}
return result;
}
private void fireAlarm( AlarmEvent event )
{
// TODO: Should possibly just be delegated to AlarmSystem
for( AlarmListener listener : alarmSystem.alarmListeners() )
{
try
{
listener.alarmFired( event );
}
catch( Exception e )
{
// ignore.
}
}
}
@Override
public String toString()
{
return "AlarmPoint[" + name() + " : " + state.currentStatus().get().name(null)
+ " : " + descriptionInDefaultLocale() + "]";
}
@Override
public void trigger( String trigger )
{
AlarmEvent event;
synchronized( this )
{
event = model.evaluate( me, trigger );
if( event == null )
{
return;
}
state.currentStatus().set( event.newStatus().get() );
history.addEvent( event, trigger );
}
fireAlarm( event );
}
@Override
public void activate()
{
trigger( AlarmPoint.TRIGGER_ACTIVATE );
}
@Override
public void deactivate()
{
trigger( AlarmPoint.TRIGGER_DEACTIVATE );
}
@Override
public void acknowledge()
{
trigger( AlarmPoint.TRIGGER_ACKNOWLEDGE );
}
@Override
public AlarmHistory history()
{
return history;
}
@Override
public AlarmStatus currentStatus()
{
return state.currentStatus().get();
}
/**
* Returns the Name of the AlarmPoint.
* This normally returns the human readable technical name of the AlarmPoint.
*/
@Override
public String name()
{
return state.systemName().get();
}
/**
* Returns a Description of the AlarmPoint.
* This normally returns a full Description of the AlarmPoint in the
* default Locale.
*/
@Override
public String descriptionInDefaultLocale()
{
return description( null );
}
/**
* Returns a Description of the AlarmPoint.
* This normally returns a full Description of the AlarmPoint in the
* Locale. If Locale is <code><b>null</b></code>, then the
* default Locale is used.
*/
@Override
public String description( Locale locale )
{
if( locale == null )
{
locale = Locale.getDefault();
}
ResourceBundle rb = ResourceBundle.getBundle( "org.qi4j.library.alarm.user.AlarmDescriptions", locale );
return rb.getString( name() );
}
@Override
public void updateCondition( boolean condition )
{
String trig = model.computeTrigger( state.currentStatus().get(), condition );
if( trig != null )
{
trigger( trig );
}
}
@Override
public boolean currentCondition()
{
return model.computeCondition( state.currentStatus().get() );
}
}
}