# AlarmPoint.java

    /*
     * 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() );
            }
        }
    }