/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.observation.remote.internal;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.component.phase.Initializable;
import org.xwiki.component.phase.InitializationException;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.context.ExecutionContextException;
import org.xwiki.context.ExecutionContextManager;
import org.xwiki.observation.ObservationManager;
import org.xwiki.observation.event.ApplicationStoppedEvent;
import org.xwiki.observation.remote.LocalEventData;
import org.xwiki.observation.remote.NetworkAdapter;
import org.xwiki.observation.remote.RemoteEventData;
import org.xwiki.observation.remote.RemoteEventException;
import org.xwiki.observation.remote.RemoteObservationManager;
import org.xwiki.observation.remote.RemoteObservationManagerConfiguration;
import org.xwiki.observation.remote.RemoteObservationManagerContext;
import org.xwiki.observation.remote.converter.EventConverterManager;

/**
 * JGoups based {@link RemoteObservationManager}. It's also the default implementation for now.
 * 
 * @version $Id$
 * @since 2.0M3
 */
@Component
@Singleton
public class DefaultRemoteObservationManager implements RemoteObservationManager, Initializable
{
    /**
     * Access {@link RemoteObservationManager} configuration.
     */
    @Inject
    private RemoteObservationManagerConfiguration configuration;

    /**
     * Used to convert local event from and to remote event.
     */
    @Inject
    private EventConverterManager eventConverterManager;

    /**
     * Used to inject event coming from network.
     */
    @Inject
    private ObservationManager observationManager;

    /**
     * Used to set some extra information about the current event injected to the local {@link ObservationManager}.
     */
    @Inject
    private RemoteObservationManagerContext remoteEventManagerContext;

    /**
     * Used to initialize ExecutionContext for the remote->local thread.
     */
    @Inject
    private Execution execution;

    /**
     * Used to initialize ExecutionContext for the remote->local thread.
     */
    @Inject
    private ExecutionContextManager executionContextManager;

    /**
     * Used to lookup the network adapter.
     */
    @Inject
    private ComponentManager componentManager;

    /**
     * The logger to log.
     */
    @Inject
    private Logger logger;

    /**
     * The network adapter to use to actually send and receive network messages.
     */
    private NetworkAdapter networkAdapter;

    /**
     * {@inheritDoc}
     * 
     * @see org.xwiki.component.phase.Initializable#initialize()
     */
    public void initialize() throws InitializationException
    {
        try {
            String networkAdapterHint = this.configuration.getNetworkAdapter();
            this.networkAdapter = this.componentManager.lookup(NetworkAdapter.class, networkAdapterHint);
        } catch (ComponentLookupException e) {
            throw new InitializationException("Failed to initialize network adapter ["
                + this.configuration.getNetworkAdapter() + "]", e);
        }

        // Start configured channels and register them against the JMX server
        for (String channelId : this.configuration.getChannels()) {
            try {
                startChannel(channelId);
            } catch (RemoteEventException e) {
                this.logger.error("Failed to start channel [" + channelId + "]", e);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.xwiki.observation.remote.RemoteObservationManager#notify(org.xwiki.observation.remote.LocalEventData)
     */
    public void notify(LocalEventData localEvent)
    {
        if (this.remoteEventManagerContext.isRemoteState()) {
            // the event is a remote event
            return;
        }

        // Convert local->remote
        RemoteEventData remoteEvent = this.eventConverterManager.createRemoteEventData(localEvent);

        // if remote event data is not filled it means the message should not be sent to the network
        if (remoteEvent != null) {
            this.networkAdapter.send(remoteEvent);
        }

        if (localEvent.getEvent() instanceof ApplicationStoppedEvent) {
            try {
                this.networkAdapter.stopAllChannels();
            } catch (RemoteEventException e) {
                this.logger.error("Failed to stop channels", e);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.xwiki.observation.remote.RemoteObservationManager#notify(org.xwiki.observation.remote.RemoteEventData)
     */
    public void notify(RemoteEventData remoteEvent)
    {
        // Make sure the Execution context is properly initialized
        initializeContext();

        LocalEventData localEvent = this.eventConverterManager.createLocalEventData(remoteEvent);

        // send event
        if (localEvent != null) {
            // Indicate all the following events are remote events
            this.remoteEventManagerContext.pushRemoteState();

            try {
                this.observationManager.notify(localEvent.getEvent(), localEvent.getSource(), localEvent.getData());
            } finally {
                // Indicate all the following events are local events
                this.remoteEventManagerContext.popRemoteState();
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.xwiki.observation.remote.RemoteObservationManager#startChannel(java.lang.String)
     */
    public void startChannel(String channelId) throws RemoteEventException
    {
        this.networkAdapter.startChannel(channelId);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.xwiki.observation.remote.RemoteObservationManager#stopChannel(java.lang.String)
     */
    public void stopChannel(String channelId) throws RemoteEventException
    {
        this.networkAdapter.stopChannel(channelId);
    }

    /**
     * Make sure an ExecutionContext initialized for remote->local thread.
     */
    private void initializeContext()
    {
        if (this.execution.getContext() == null) {
            ExecutionContext context = new ExecutionContext();

            try {
                this.executionContextManager.initialize(context);
            } catch (ExecutionContextException e) {
                this.logger.error("failed to initialize execution context", e);
            }

            this.execution.setContext(context);
        }
    }
}

@@ -1372,60 +1372,8 @@
 xt;%0A
-import org.xwiki.context.ExecutionContextException;%0A
 impo
@@ -7230,32 +7230,80 @@
 cutionContext();
+%0A            this.execution.setContext(context);
 %0A%0A            tr
@@ -7393,32 +7393,16 @@
  catch (
-ExecutionContext
 Exceptio
@@ -7484,24 +7484,24 @@
 ntext%22, e);%0A
+
             
@@ -7505,57 +7505,8 @@
    %7D
-%0A%0A            this.execution.setContext(context);
 %0A   

Document cache not updated in a cluster