# AlarmSystem.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.Date;
    import java.util.Iterator;
    import java.util.List;
    import java.util.concurrent.CopyOnWriteArrayList;
    import org.qi4j.api.entity.EntityBuilder;
    import org.qi4j.api.injection.scope.Service;
    import org.qi4j.api.injection.scope.Structure;
    import org.qi4j.api.mixin.Mixins;
    import org.qi4j.api.query.Query;
    import org.qi4j.api.query.QueryBuilder;
    import org.qi4j.api.query.QueryBuilderFactory;
    import org.qi4j.api.service.ServiceReference;
    import org.qi4j.api.unitofwork.UnitOfWork;
    import org.qi4j.api.unitofwork.UnitOfWorkFactory;
    import org.qi4j.api.value.ValueBuilder;
    import org.qi4j.api.value.ValueBuilderFactory;

    /**
     * Defines the AlarmSystem interface.
     *
     * <p>
     * The <code>AlarmSystem</code> is a central registry/handler for all
     * <code>AlarmPoint</code> objects. By registering <code>AlarmListener</code>s
     * to the AlarmSystem, objects are able to 'ignore' the fact that there
     * are many <code>AlarmPoint</code> objects in the system.
     * </p>
     * <p>
     * Also, new attributes registered with the AlarmSystem will propagate into
     * all existing and future <code>AlarmPoint</code>s, whereas attributes at <code>
     * AlarmPoint</code> level is individual to an <code>AlarmPoint</code>.
     * </p>
     * <p>
     * Many different AlarmModels can co-exist in the same application.
     * In fact, every AlarmPoint can have its own AlarmModel, and the AlarmModel
     * can be changed in runtime, for unrivaled flexibility. However, typically
     * the AlarmModel is set in the AlarmSystem only, and all Alarms will
     * use the default model.
     * </p>
     * <p>The default alarm model is a service set by during assembly;
     * </p>
     * <pre><code>
     *   public void assemble( ModuleAssembly module )
     *       throws AssemblyException
     *   {
     *       module.addServices( AlarmSystemService.class );
     *       module.addServices( StandardAlarmModelService.class );
     *   }
     * </code></pre>
     *
     * @author Niclas Hedhman
     */
    @Mixins( AlarmSystemService.AlarmSystemMixin.class )
    public interface AlarmSystem
    {

        /**
         * Returns all the AlarmModels that has been installed.
         *
         * @return all the AlarmModels that has been installed.
         */
        List<AlarmModelDescriptor> alarmModels();

        /**
         * Returns the default AlarmModel.
         *
         * @return the default AlarmModel in this AlarmSystem.
         */
        AlarmModel defaultAlarmModel();

        /**
         * Returns a list of all Alarms registered to the service.
         * <p>
         * The returned Collection may not be modified in any way. The
         * implementation is free to return a clone, but not required to do
         * so, and may decide to terminate if the collection is modified.
         * </p>
         *
         * @return a list of all Alarms registered to the service.
         */
        Query<AlarmPoint> alarmList();

        /**
         * Creates an AlarmPoint with the default AlarmModel.
         *
         * @param name the name of the AlarmPoint to be created.
         *
         * @param category The category the created AlarmPoint should belong to.
         *
         * @return the created AlarmPoint with the given name using the default AlarmModel.
         */
        AlarmPoint createAlarm( String name, AlarmCategory category );

        /**
         * Register AlarmListener to recieve <code>AlarmEvents</code> from all
         * <code>Alarms</code> managed by this <code>AlarmSystem</code>.
         *
         * @param listener the global listener to be added.
         */
        void addAlarmListener( AlarmListener listener );

        /**
         * Remove the <code>AlarmListener</code> from the <code>AlarmSystem</code>.
         *
         * @param listener the global listener to be removed.
         */
        void removeAlarmListener( AlarmListener listener );

        /**
         * Returns an immmutable list of all AlarmListeners registered to the service.
         *
         * @return a list of all AlarmListeners registered to the service.
         */
        List<AlarmListener> alarmListeners();

        /**
         * AlarmSystem implementation.
         */
        class AlarmSystemMixin
            implements AlarmSystem
        {
            @Service
            private Iterable<ServiceReference<AlarmModel>> models;

            private final CopyOnWriteArrayList<AlarmListener> alarmListeners;

            @Structure
            private UnitOfWorkFactory uowf;

            @Structure
            private ValueBuilderFactory vbf;

            @Structure
            private QueryBuilderFactory qbf;

            public AlarmSystemMixin()
            {
                alarmListeners = new CopyOnWriteArrayList<AlarmListener>();
            }

            /**
             * Returns all the AlarmModels that has been installed.
             */
            @Override
            public List<AlarmModelDescriptor> alarmModels()
            {
                List<AlarmModelDescriptor> descriptors = new ArrayList<AlarmModelDescriptor>();
                for( ServiceReference<AlarmModel> model : models )
                {
                    descriptors.add( model.metaInfo( AlarmModelDescriptor.class ) );
                }
                return descriptors;
            }

            /**
             * Returns the default AlarmModel.
             */
            @Override
            public AlarmModel defaultAlarmModel()
            {
                AlarmModelDescriptor defaultDefault = null;
                for( AlarmModelDescriptor descriptor : alarmModels() )
                {
                    if( descriptor.isDefaultModel() )
                    {
                        return alarmModel( descriptor );
                    }
                    defaultDefault = descriptor;
                }
                return alarmModel( defaultDefault );
            }

            private AlarmModel alarmModel( AlarmModelDescriptor descriptor )
            {
                for( ServiceReference<AlarmModel> model : models )
                {
                    if( model.metaInfo( AlarmModelDescriptor.class ).equals( descriptor ) )
                    {
                        return model.get();
                    }
                }
                return null;
            }

            /**
             * Creates an AlarmPoint with the default AlarmModel.
             * @param name The system name of the AlarmPoint.
             * @param category The AlarmPoint Category the created alarm should belong to.
             */
            @Override
            public AlarmPoint createAlarm( String name, AlarmCategory category )
            {
                UnitOfWork uow = uowf.currentUnitOfWork();
                EntityBuilder<AlarmPoint> builder = uow.newEntityBuilder( AlarmPoint.class );
                builder.instance().category().set( category );
                AlarmPoint.AlarmState state = builder.instanceFor( AlarmPoint.AlarmState.class );
                state.systemName().set( name );
                state.currentStatus().set( createStatus( AlarmPoint.STATUS_NORMAL ) );
                return builder.newInstance();
            }

            private AlarmStatus createStatus( String status )
            {
                ValueBuilder<AlarmStatus> builder = vbf.newValueBuilder( AlarmStatus.class );
                AlarmStatus.State statePrototype = builder.prototypeFor( AlarmStatus.State.class );
                statePrototype.name().set( status );
                statePrototype.creationDate().set( new Date() );
                return builder.newInstance();
            }

            /**
             * Register AlarmListener to recieve <code>AlarmEvents</code> from all
             * <code>Alarms</code> managed by this <code>AlarmSystem</code>.
             */
            @Override
            public void addAlarmListener( AlarmListener listener )
            {
                alarmListeners.add( listener );
            }

            /**
             * Remove the <code>AlarmListener</code> from the <code>AlarmSystem</code>.
             */
            @Override
            public void removeAlarmListener( AlarmListener listener )
            {
                alarmListeners.remove( listener );
            }

            /**
             * Returns a list of all Alarms registered to the service.
             */
            @Override
            public Query<AlarmPoint> alarmList()
            {
                UnitOfWork uow = uowf.currentUnitOfWork();
                QueryBuilder<AlarmPoint> builder = qbf.newQueryBuilder( AlarmPoint.class );
                return uow.newQuery( builder );
            }

            @Override
            public List<AlarmListener> alarmListeners()
            {
                synchronized( alarmListeners )
                {
                    return alarmListeners;
                }
            }

            public void alarmFired( AlarmEvent event )
            {
                Iterator list;
                //noinspection SynchronizeOnNonFinalField
                synchronized( alarmListeners )
                {
                    list = alarmListeners.iterator();
                }
                while( list.hasNext() )
                {
                    AlarmListener listener = (AlarmListener) list.next();
                    try
                    {
                        listener.alarmFired( event );
                    }
                    catch( Exception e )
                    {
                        // TODO: Utilize a logger system instead.
                        System.err.println( "Exception in AlarmListener: " + listener );
                        e.printStackTrace();
                    }
                }
            }
        }
    }