/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.apache.synapse.mediators;

import org.apache.axis2.AxisFault;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.FaultHandler;
import org.apache.synapse.Mediator;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.SynapseException;
import org.apache.synapse.aspects.flow.statistics.collectors.CloseEventCollector;
import org.apache.synapse.aspects.flow.statistics.collectors.FaultStatisticCollector;
import org.apache.synapse.aspects.flow.statistics.collectors.RuntimeStatisticCollector;
import org.apache.synapse.continuation.ContinuationStackManager;
import org.apache.synapse.mediators.base.SequenceMediator;
import org.apache.synapse.util.ConcurrencyThrottlingUtils;

import org.apache.synapse.unittest.UnitTestModeUtils;

import static org.apache.synapse.unittest.Constants.COVERAGE_ARTIFACT_KEY;

/**
 * This implements the FaultHandler interface as a mediator fault handler. That is the fault handler is
 * specified by a sequence and this handler implements the logic of handling the fault through the set
 * of mediators present in the sequence.
 *
 * @see org.apache.synapse.FaultHandler
 */
public class MediatorFaultHandler extends FaultHandler {

    private static final Log log = LogFactory.getLog(MediatorFaultHandler.class);
    private static final Log trace = LogFactory.getLog(SynapseConstants.TRACE_LOGGER);

    /**
     * This holds the fault sequence for the mediator fault handler
     */
    private Mediator faultMediator = null;

    /**
     * Constructs the FaultHandler object for handling mediator faults
     *
     * @param faultMediator Mediator in which fault sequence is specified
     */
    public MediatorFaultHandler(Mediator faultMediator) {

        this.faultMediator = faultMediator;
    }

    /**
     * Implements the fault handling method for the mediators (basically sequences)
     *
     * @param synCtx Synapse Message Context of which mediation occurs
     * @throws SynapseException in case there is a failure in the fault execution
     * @see org.apache.synapse.FaultHandler#handleFault(org.apache.synapse.MessageContext)
     */
    public void onFault(MessageContext synCtx) throws SynapseException {

        boolean traceOn = synCtx.getTracingState() == SynapseConstants.TRACING_ON;
        boolean traceOrDebugOn = traceOn || log.isDebugEnabled();
        boolean isStatisticsEnabled = RuntimeStatisticCollector.isStatisticsEnabled();
        if (isStatisticsEnabled) {
            FaultStatisticCollector.reportFault(synCtx);
        }

        String name = null;
        if (faultMediator instanceof SequenceMediator) {
            name = ((SequenceMediator) faultMediator).getName();
            ContinuationStackManager.clearStack(synCtx);
            synCtx.setProperty(SynapseConstants.CONTINUATION_CALL, false);
        }
        if (name == null) {
            name = faultMediator.getClass().getName();
        }

        if (traceOrDebugOn) {
            traceOrDebugWarn(traceOn, "Executing fault handler mediator : " + name);
        }
        ConcurrencyThrottlingUtils.decrementConcurrencyThrottleAccessController(synCtx);
        synCtx.getServiceLog().warn("Executing fault sequence mediator : " + name);
        
        // Handle coverage tracking for fault/onError sequences in unit tests
        String originalArtifactKey = handleCoverageForFaultSequence(synCtx);
        
        this.faultMediator.mediate(synCtx);
        
        // Restore original artifact key for unit test coverage
        if (originalArtifactKey != null) {
            synCtx.setProperty(COVERAGE_ARTIFACT_KEY, originalArtifactKey);
        }
        
        if(isStatisticsEnabled) {
            CloseEventCollector.closeFlowForcefully(synCtx, true);
        }
    }

    /**
     * Getter for the mediator describing the fault sequence
     *
     * @return Mediator specifying the fault sequence for mediator fault handler
     */
    public Mediator getFaultMediator() {
        return faultMediator;
    }

    /**
     * Setter of the mediator describing the fault sequence
     *
     * @param faultMediator Mediator specifying the fault sequence to be used by the handler
     */
    public void setFaultMediator(Mediator faultMediator) {
        this.faultMediator = faultMediator;
    }

    /**
     * Handle coverage tracking when executing a fault/onError sequence during unit tests.
     * Sets the COVERAGE_ARTIFACT_KEY to the fault sequence's key so mediators inside
     * the fault sequence are tracked correctly.
     *
     * @param synCtx message context
     * @return the original artifact key (to be restored after sequence execution), or null if not in unit test mode
     */
    private String handleCoverageForFaultSequence(MessageContext synCtx) {
        if (!UnitTestModeUtils.isUnitTestMode()) {
            return null;
        }

        String originalKey = (String) synCtx.getProperty(COVERAGE_ARTIFACT_KEY);

        if (faultMediator instanceof SequenceMediator) {
            SequenceMediator faultSeq = (SequenceMediator) faultMediator;
            String faultSeqName = faultSeq.getName();
            if (faultSeqName != null && !faultSeqName.isEmpty()) {
                String faultSeqKey = "Sequence:" + faultSeqName;
                synCtx.setProperty(COVERAGE_ARTIFACT_KEY, faultSeqKey);
            }
        }

        return originalKey;
    }

    private void traceOrDebugWarn(boolean traceOn, String msg) {
        if (traceOn) {
            trace.warn(msg);
        }
        log.warn(msg);
    }

}
