# SourceContext.java

    /**
     *  Copyright (c) 2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
     *
     *  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.apache.synapse.transport.passthru;

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.apache.http.HttpRequest;
    import org.apache.http.nio.NHttpConnection;
    import org.apache.synapse.commons.logger.ContextAwareLogger;
    import org.apache.synapse.transport.passthru.config.PassThroughCorrelationConfigDataHolder;
    import org.apache.synapse.transport.passthru.config.SourceConfiguration;
    import org.apache.synapse.transport.passthru.util.ControlledByteBuffer;

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;

    /**
     * This class represents the information about a TCP Connection at a given point in time.
     * In a Single TCP Connection there can be multiple HTTP Requests.
     */
    public class SourceContext {
        public static final String CONNECTION_INFORMATION = "CONNECTION_INFORMATION";

        /** logger for correlation.log */
        private static final Log correlationLog = LogFactory.getLog(PassThroughConstants.CORRELATION_LOGGER);

        private SourceConfiguration sourceConfiguration;

        private ProtocolState state = ProtocolState.REQUEST_READY;

        private SourceRequest request;

        private SourceResponse response;

        /** Mark the connection to be shut down after the current request-response is completed. */
        private boolean shutDown = false;

        private Pipe reader;

        private Pipe writer;

        private Lock lock = new ReentrantLock();

        /** Time that state got updated*/
        private long lastStateUpdatedTime;

        public SourceContext(SourceConfiguration sourceConfiguration) {
            this.sourceConfiguration = sourceConfiguration;
        }

        public SourceConfiguration getSourceConfiguration() {
            return sourceConfiguration;
        }

        public ProtocolState getState() {
            return state;
        }

        public void setState(ProtocolState state) {
            this.state = state;
        }

        public SourceRequest getRequest() {
            return request;
        }

        public void setRequest(SourceRequest request) {
            this.request = request;
        }

        public SourceResponse getResponse() {
            return response;
        }

        public void setResponse(SourceResponse response) {
            this.response = response;
        }

        /**
         * Reset the resources associated with this context
         */
        public void reset() {
    		reset(false);
    	}

        /**
         * Reset the resources associated with this context
         *
         * @param isError whether an error is causing this shutdown of the connection.
         *                It is very important to set this flag correctly.
         *                When an error causing the shutdown of the connections we should not
         *                release associated writer buffer to the pool as it might lead into
         *                situations like same buffer is getting released to both source and target
         *                buffer factories
         */
        public void reset(boolean isError) {
    		this.request = null;
    		this.response = null;
    		this.state = ProtocolState.REQUEST_READY;

    		if (writer != null) {
    			if (!isError) {      // If there is an error we do not release the buffer to the factory
                    ControlledByteBuffer buffer = writer.getBuffer();
    				sourceConfiguration.getBufferFactory().release(buffer);
    			}
    		}

    		this.reader = null;
    		this.writer = null;
    	}

        public Lock getLock() {
            return lock;
        }

        public boolean isShutDown() {
            return shutDown;
        }

        public void setShutDown(boolean shutDown) {
            this.shutDown = shutDown;
        }

        public Pipe getReader() {
            return reader;
        }

        public void setReader(Pipe reader) {
            this.reader = reader;
        }

        public Pipe getWriter() {
            return writer;
        }

        public void setWriter(Pipe writer) {
            this.writer = writer;
        }

        public static void create(NHttpConnection conn, ProtocolState state,
                                  SourceConfiguration configuration) {
            SourceContext sourceContext = new SourceContext(configuration);
            conn.getContext().setAttribute(CONNECTION_INFORMATION, sourceContext);
            sourceContext.setState(state);

            if (PassThroughCorrelationConfigDataHolder.isEnable()) {
                sourceContext.updateLastStateUpdatedTime();
            }
        }

        public static void updateState(NHttpConnection conn, ProtocolState state) {
            SourceContext sourceContext = (SourceContext)
                    conn.getContext().getAttribute(CONNECTION_INFORMATION);
            if (sourceContext != null) {
                if(sourceContext.getState() == state) {
                    return;
                }
                if (PassThroughCorrelationConfigDataHolder.isEnable()) {
                    long lastStateUpdateTime = sourceContext.getLastStateUpdatedTime();
                    // If the Correlation Logs are toggled (off -> on) during runtime, we need to reset the last state updated time.
                    if (PassThroughCorrelationConfigDataHolder.isToggled()) {
                        lastStateUpdateTime = sourceContext.updateLastStateUpdatedTime();
                        PassThroughCorrelationConfigDataHolder.resetToggled();
                    }
                    String url = "", method = "";
                    if (sourceContext.getRequest() != null) {
                        url = sourceContext.getRequest().getUri();
                        method = sourceContext.getRequest().getMethod();
                    } else {
                        HttpRequest httpRequest = conn.getHttpRequest();
                        if (httpRequest != null) {
                            url = httpRequest.getRequestLine().getUri();
                            method = httpRequest.getRequestLine().getMethod();
                        }
                    }
                    if ((method.length() != 0) && (url.length() != 0)) {
                        ContextAwareLogger.getLogger(conn.getContext(), correlationLog, false)
                                .info((sourceContext.updateLastStateUpdatedTime() - lastStateUpdateTime)
                                        + "|HTTP State Transition|"
                                        + conn.getContext().getAttribute("http.connection") + "|"
                                        + method + "|" + url + "|"
                                        + state.name());
                    }
                }
                sourceContext.setState(state);
            } else {
                throw new IllegalStateException("Connection information should be present");
            }
        }

        public static boolean assertState(NHttpConnection conn, ProtocolState state) {
            SourceContext info = (SourceContext)
                    conn.getContext().getAttribute(CONNECTION_INFORMATION);

            return info != null && info.getState() == state;

        }

        public static ProtocolState getState(NHttpConnection conn) {
            SourceContext info = (SourceContext)
                    conn.getContext().getAttribute(CONNECTION_INFORMATION);

            return info != null ? info.getState() : null;
        }

        public static void setRequest(NHttpConnection conn, SourceRequest request) {
            SourceContext info = (SourceContext)
                    conn.getContext().getAttribute(CONNECTION_INFORMATION);

            if (info != null) {
                info.setRequest(request);
            } else {
                throw new IllegalStateException("Connection information should be present");
            }
        }

        public static void setResponse(NHttpConnection conn, SourceResponse response) {
            SourceContext info = (SourceContext)
                    conn.getContext().getAttribute(CONNECTION_INFORMATION);

            if (info != null) {
                info.setResponse(response);
            } else {
                throw new IllegalStateException("Connection information should be present");
            }
        }

        public static SourceRequest getRequest(NHttpConnection conn) {
            SourceContext info = (SourceContext)
                    conn.getContext().getAttribute(CONNECTION_INFORMATION);

            return info != null ? info.getRequest() : null;
        }

        public static SourceResponse getResponse(NHttpConnection conn) {
            SourceContext info = (SourceContext)
                    conn.getContext().getAttribute(CONNECTION_INFORMATION);

            return info != null ? info.getResponse() : null;
        }

        public static SourceContext get(NHttpConnection conn) {
            return (SourceContext) conn.getContext().getAttribute(CONNECTION_INFORMATION);
        }

        public static Lock getLock(NHttpConnection conn) {
            SourceContext info = (SourceContext)
                    conn.getContext().getAttribute(CONNECTION_INFORMATION);

            return info != null ? info.getLock() : null;
        }

        public long getLastStateUpdatedTime() {
            return lastStateUpdatedTime;
        }

        public long updateLastStateUpdatedTime() {
            this.lastStateUpdatedTime = System.currentTimeMillis();
            return this.lastStateUpdatedTime;
        }
    }