public enum Method {

    GET, POST, PUT, TRACE, OPTIONS, HEAD, DELETE
} public interface CapturedRequest {

    String getPath();
    
    String getRequestLine();
    
    String getMethod();
    
    List<String> getHeaders();
    
    byte[] getBody();
    
    String getBody(String encoding);
    
    /**
     * Returns true if this request was broadcast to any subscribers.
     */
    boolean isBroadcast();
} public class SimpleCapturedRequest implements CapturedRequest {

    private final Request request;
    
    private boolean broadcast = false;
    
    public SimpleCapturedRequest(Request request) {
        this.request = request;
    }
    
    public String getPath() {
        
        return request.getPath().getPath();
    }

    public String getRequestLine() {
        
        return headerLines()[0];
    }

    public String getMethod() {
        
        return request.getMethod();
    }

    public List<String> getHeaders() {
        
        List<String> headers = new ArrayList<String>();
        String[] lines = headerLines();
        for (int i = 1; i < lines.length; i++) {
            headers.add(lines[i]);
        }
        
        return headers;
    }

    public byte[] getBody() {
        
        try {
            return RequestUtils.readBody(request.getInputStream());
        } catch (IOException e) {
            throw new RuntimeException("error getting body", e);
        }
    }

    public String getBody(String encoding) {
        
        try {
            
            return new String(getBody(), encoding);
            
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("error getting encoded body", e);
        }
    }
    
    private String[] headerLines() {
        
        String header = request.getHeader().toString();
        return header.split("\\r?\\n");
    }
    
    public void setBroadcast(boolean broadcast) {
        
        this.broadcast = broadcast;
    }
    
    public boolean isBroadcast() {
        
        return broadcast;
    }
} public class RequestHandlerImpl implements RequestHandler {

    private int statusCode = -1;
    private String contentType;
    private String body;
    private SessionHandler sessionHandler;
    private long delay = -1;
    private TimeUnit delayUnit;
    private long period = -1;
    private TimeUnit periodUnit;
    private int periodTimes = -1;
    private long timeout = -1;
    private TimeUnit timeoutUnit;
    private Upon upon;
    private Set<SimpleImmutableEntry<String, String>> headers = 
            new HashSet<SimpleImmutableEntry<String,String>>();
    
    private Object entity;
    
    private HttpRequestHandler httpHandler;
    
    private final FixtureContainer container;
    
    public RequestHandlerImpl(FixtureContainer container) {
        this.container = container;
    }
    
    public RequestHandler with(int statusCode, String contentType, String body) {
        
        this.statusCode = statusCode;
        this.contentType = contentType;
        this.body = body;
        return this;
    }
    
    public RequestHandler with(int statusCode, String contentType, Object entity) {
        
        this.statusCode = statusCode;
        this.contentType = contentType;
        this.entity = entity;
        return this;
    }
    
    public RequestHandler with(HttpRequestHandler customHandler) {
        
        this.httpHandler = customHandler;
        return this;
    }

    public RequestHandler withSessionHandler(SessionHandler sessionHandler) {
        
        this.sessionHandler = sessionHandler;
        return this;
    }
    
    public RequestHandler withHeader(String name, String value) {
        
        headers.add(new SimpleImmutableEntry<String, String>(name, value));
        return this;
    }
    
    public RequestHandler after(long delay, TimeUnit delayUnit) {
        
        this.delay = delay;
        this.delayUnit = delayUnit;
        return this;
    }
    
    public RequestHandler every(long period, TimeUnit periodUnit) {
        
        this.period = period;
        this.periodUnit = periodUnit;
        return this;
    }
    
    public RequestHandler every(long period, TimeUnit periodUnit, int times) {
        
        this.periodTimes = times;
        return every(period, periodUnit);
    }
    
    public RequestHandler withTimeout(long timeout, TimeUnit timeoutUnit) {
        
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
        return this;
    }
    
    public RequestHandler upon(Method method, String resource) {
        
        this.upon = new Upon(method, resource, this);
        container.addUponHandler(this.upon);
        return this;
    }
    
    public RequestHandler upon(Method method, String resource, String contentType) {
        
        this.upon = new Upon(method, resource, contentType, this);
        container.addUponHandler(this.upon);
        return this;
    }
    
    public RequestHandler withRedirect(String location) {
        
        return withRedirect(location, Status.FOUND.code);
    }
    
    public RequestHandler withRedirect(String location, int statusCode) {
        
        this.statusCode = statusCode;
        this.contentType = "text/plain";
        this.body = "";
        withHeader("Location", location);
        withHeader("Connection", "close");
        return this;
    }
    
    /*-----------------------------------------*/
    
    public int statusCode() {
        return statusCode;
    }

    public String contentType() {
        return contentType;
    }

    public ResponseBody body(HttpRequest request, Response resp, 
            MarshallerProvider marshallerProvider) {
        
        if (httpHandler != null) {
            
            SimpleHttpResponse response = new SimpleHttpResponse(request, resp);
            httpHandler.handle(request, response);
            this.contentType = response.getContentType();
            this.statusCode = response.getStatusCode();
            return response.getBody(marshallerProvider);
        }
        
        if (hasEntity()) {
            
            Marshaller marshaller = marshallerProvider.getMarshaller(contentType);
            if (marshaller == null) {
                throw new RuntimeException("an entity has been set in the " +
                		"response, but no marshaller exists for " +
                		"content type: " + contentType);
            }
            
            return new MarshalledResponseBody(entity, marshaller);
        }
        
        return new InterpolatedResponseBody(body, request);
    }
    
    public SessionHandler sessionHandler() {
        return sessionHandler;
    }
    
    public boolean isAsync() {
        return delay > -1 || period > -1 || isSuspend();
    }
    
    public long delay() {
        return delay;
    }
    
    public TimeUnit delayUnit() {
        return delayUnit;
    }
    
    public long period() {
        return period;
    }
    
    public TimeUnit periodUnit() {
        return periodUnit;
    }
    
    public int periodTimes() {
        return periodTimes;
    }
    
    public boolean hasTimeout() {
        return timeout != -1;
    }
    
    public long timeout() {
        return timeout;
    }
    
    public TimeUnit timeoutUnit() {
        return timeoutUnit;
    }
    
    public Upon upon() {
        return upon;
    }
    
    public boolean isSuspend() {
        return upon != null;
    }
    
    public Set<SimpleImmutableEntry<String, String>> headers() {
        return new HashSet<SimpleImmutableEntry<String, String>>(headers);
    }
    
    public HttpRequestHandler customHandler() {
        return httpHandler;
    }
    
    public Object entity() {
        return entity;
    }
    
    public boolean hasEntity() {
        return entity != null;
    }
    
    public boolean hasContentType() {
        return contentType != null;
    }
} public class RequestMarshallerImpl implements RequestMarshaller {

    private final String contentType;
    
    private Marshaller marshaller;
    
    public RequestMarshallerImpl(String contentType) {
        this.contentType = contentType;
    }
    
    public void with(Marshaller marshaller) {
        this.marshaller = marshaller;
    }
    
    public String getContentType() {
        return contentType;
    }
    
    public Marshaller getMarshaller() {
        return marshaller;
    }
} public class RequestUnmarshallerImpl implements RequestUnmarshaller {

private final String contentType;
    
    private Unmarshaller unmarshaller;
    
    public RequestUnmarshallerImpl(String contentType) {
        this.contentType = contentType;
    }
    
    public void with(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }
    
    public String getContentType() {
        return contentType;
    }
    
    public Unmarshaller getUnmarshaller() {
        return unmarshaller;
    }
} public class Upon {

    private final Method method;
    private final String resource;
    private final String contentType;
    private final RequestHandlerImpl handler;
    
    public Upon(Method method, String resource, RequestHandlerImpl handler) {
        this(method, resource, null, handler);
    }
    
    public Upon(Method method, String resource, String contentType, 
            RequestHandlerImpl handler) {
        
        this.method = method;
        this.resource = resource;
        this.contentType = contentType;
        this.handler = handler;
    }

    public Method getMethod() {
        return method;
    }
    
    public String getResource() {
        return resource;
    }
    
    public String getContentType() {
        return contentType;
    }
    
    public RequestHandlerImpl getHandler() {
        return handler;
    }
} public class AsyncHandler {

    private final List<Subscriber> subscribers = 
            Collections.synchronizedList(new ArrayList<Subscriber>());
    
    private final ExecutorService asyncExecutor;
    
    public AsyncHandler(ExecutorService asyncExecutor) {
        
        this.asyncExecutor = asyncExecutor;
    }
    
    public void doAsync(Request request, Response response, RequestHandlerImpl handler, 
            String responseContentType, ResponseBody responseBody,
            MarshallerProvider marshallerProvider,
            UnmarshallerProvider unmarshallerProvider) {
        
        AsyncTask task = new AsyncTask(request, response, handler, subscribers,
                responseContentType, responseBody, 
                marshallerProvider, unmarshallerProvider);
        asyncExecutor.execute(task);
    }
    
    public void broadcastToSubscribers(Request request, Route route, Upon upon, 
            SimpleCapturedRequest captured) {
        
        synchronized (subscribers) {
            Broadcast broadcast = new Broadcast(request, route, upon, captured);
            for (Subscriber subscriber : subscribers) {
                if (broadcast.isFor(subscriber)) {
                    subscriber.addNextBroadcast(broadcast);
                }
            }
        }
    }
    
    public void stop() {
        
        asyncExecutor.shutdown();
    }
} public abstract class ResponseBody {

    public abstract void send(Response resp, String contentType);
    
    public abstract void sendAndCommit(Response resp, String contentType);
    
    public abstract boolean hasContent();
    
    protected void addStandardHeaders(Response response, String responseContentType) {
        
        long time = System.currentTimeMillis();
        response.setValue("Content-Type", responseContentType);
        response.setValue("Server", "Fixd/1.0 (Simple 5.1.4)");
        response.setDate("Date", time);
        response.setDate("Last-Modified", time);
    }
} public class StringResponseBody extends ResponseBody {

    private final String body;
    
    public StringResponseBody(String body) {
        this.body = body;
    }
    
    public boolean hasContent() {
        return body != null && body.trim().length() > 0;
    }
    
    @Override
    public void send(Response response, String contentType) {
        try {
            printBody(response, contentType);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void sendAndCommit(Response response, String contentType) {
        try {
            PrintStream ps = printBody(response, contentType);
            ps.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    private PrintStream printBody(Response response, String contentType) throws IOException {
        
        PrintStream ps = response.getPrintStream();
        addStandardHeaders(response, contentType);
        ps.println(body);
        ps.flush();
        return ps;
    }
} public class SimpleHttpRequest implements HttpRequest {

    private final Request request;
    
    private final Session session;
    
    private final Route route;
    
    private final UnmarshallerProvider unmarshallerProvider;
    
    public SimpleHttpRequest(Request request, Session session, Route route, 
            UnmarshallerProvider unmarshallerProvider) {
        
        this.request = request;
        this.session = session;
        this.route = route;
        this.unmarshallerProvider = unmarshallerProvider;
    }
    
    public String getPath() {
        
        return request.getPath().getPath();
    }
    
    public String getUndecodedPath() {
        
        return RequestUtils.getUndecodedPath(request);
    }

    public Set<String> getRequestParameterNames() {
        
        return request.getQuery().keySet();
    }

    public String getRequestParameter(String name) {
        
        return request.getParameter(name);
    }
    
    public String getPathParameter(String name) {
        
        return getRoute().getNamedParameter(name, getPath());
    }
    
    public List<String> getHeaderNames() {
        
        return request.getNames();
    }
    
    public String getHeaderValue(String name) {
        
        return request.getValue(name);
    }
    
    public String getBody() {
        
        try {
            return new String(RequestUtils.readBody(getBodyAsStream()));
        } catch (IOException e) {
            throw new RuntimeException("error getting body", e);
        }
    }
    
    public InputStream getBodyAsStream() throws IOException {
        
        return request.getInputStream();
    }
    
    public <T> T getBody(Class<T> type) {
        
        Unmarshaller unmarshaller = unmarshallerProvider.getUnmarshaller(getContentType());
        if (unmarshaller == null) {
            throw new RuntimeException("cannot unmarshall body as type " + 
                    type.getName() + ", as no unmarshaller is available for" +
                    		"content-type: " + getContentType());
        }
        try {
            return unmarshaller.unmarshal(getBodyAsStream(), type);
        } catch (Exception e) {
            throw new RuntimeException("error unmarshalling", e);
        }
    }
    
    public long getContentLength() {
        
        return request.getContentLength();
    }
    
    public String getContentType() {
        
        return request.getContentType().getType();
    }
    
    public String getMethod() {
        
        return request.getMethod();
    }
    
    public long getTime() {
        
        return request.getRequestTime();
    }
    
    public String getQuery() {
        
        return request.getQuery().toString();
    }
    
    public int getMajor() {
        
        return request.getMajor();
    }
    
    public int getMinor() {
        
        return request.getMinor();
    }
    
    public String getTarget() {
        
        return request.getTarget();
    }
    
    public Session getSession() {
        
        return session;
    }
    
    public Route getRoute() {
        
        return route;
    }
} public class Session {

    private final String sessionId;
    
    private final Map<String, Object> values = new HashMap<String, Object>();
    
    private boolean valid = true;
    
    public Session() {
        
        this.sessionId = UUID.randomUUID().toString();
    }
    
    public String getSessionId() {
        
        return sessionId;
    }
    
    public Object get(String key) {
        
        return values.get(key);
    }
    
    public void set(String key, Object value) {
        
        values.put(key, value);
    }
    
    public Set<String> getAttributeNames() {
        
        return values.keySet();
    }
    
    public boolean isValid() {
        
        return valid;
    }
    
    public void invalidate() {
        
        this.valid = false;
    }
} public interface SessionHandler {

    void onCreate(HttpRequest request);
}
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executors;
import org.bigtesting.fixd.Method;
import org.bigtesting.fixd.capture.CapturedRequest;
import org.bigtesting.fixd.capture.impl.SimpleCapturedRequest;
import org.bigtesting.fixd.core.RequestHandlerImpl;
import org.bigtesting.fixd.core.RequestMarshallerImpl;
import org.bigtesting.fixd.core.RequestUnmarshallerImpl;
import org.bigtesting.fixd.core.Upon;
import org.bigtesting.fixd.core.async.AsyncHandler;
import org.bigtesting.fixd.core.body.ResponseBody;
import org.bigtesting.fixd.core.body.StringResponseBody;
import org.bigtesting.fixd.request.impl.SimpleHttpRequest;
import org.bigtesting.fixd.session.Session;
import org.bigtesting.fixd.session.SessionHandler;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import org.simpleframework.http.Status;
import org.simpleframework.http.core.Container;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*
 * Copyright (C) 2015 BigTesting.org
 *
 * 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.bigtesting.fixd.core.container;



/**
 * 
 * @author Luis Antunes
 */
public class FixtureContainer implements Container {
    
    private static final Logger logger = LoggerFactory.getLogger(FixtureContainer.class);
    
    private final RequestResolver requestResolver = new RequestResolver();
    
    private final MarshallerContainer marshallerContainer = new MarshallerContainer();
    
    private final SessionManager sessionManager = new SessionManager();
    
    private final CapturedRequestContainer capturedRequestContainer = new CapturedRequestContainer();
    
    private final AsyncHandler asyncHandler;
    
    public FixtureContainer() {
        asyncHandler = new AsyncHandler(Executors.newCachedThreadPool());
    }
    
    public FixtureContainer(int aysncThreadPoolSize) {
        asyncHandler = new AsyncHandler(Executors.newFixedThreadPool(aysncThreadPoolSize));
    }
    
    public HandlerKey addHandler(RequestHandlerImpl handler, 
            Method method, String resource) {
        
        return addHandler(handler, method, resource, null);
    }
    
    public HandlerKey addHandler(RequestHandlerImpl handler, 
            Method method, String resource, String contentType) {
        return requestResolver.addHandler(handler, method, resource, contentType);
    }
    
    public void addUponHandler(Upon upon) {
        requestResolver.addUponHandler(this, upon);
    }
    
    public Queue<CapturedRequest> getCapturedRequests() {
        return capturedRequestContainer.getCapturedRequests();
    }
    
    public CapturedRequest nextCapturedRequest() {
        return capturedRequestContainer.nextCapturedRequest();
    }
    
    public void setCapturedRequestLimit(int limit) {
        capturedRequestContainer.setCapturedRequestLimit(limit);
    }
    
    public void addContentMarshaller(String contentType, RequestMarshallerImpl marshaller) {