All Downloads are FREE. Search and download functionalities are using the official Maven repository.

restx.specs.RestxSpecTape Maven / Gradle / Ivy

There is a newer version: 1.2.0-rc2
Show newest version
package restx.specs;

import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharSource;
import com.google.common.io.CharStreams;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import restx.common.ThreadLocalMillisProvider;
import restx.http.HttpStatus;
import restx.security.RestxSessionCookieFilter;

import java.util.Iterator;
import restx.RestxRequest;
import restx.RestxRequestWrapper;
import restx.RestxResponse;
import restx.RestxResponseWrapper;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * User: xavierhanin
 * Date: 3/17/13
 * Time: 5:05 PM
 */
public class RestxSpecTape {

    private static final ReentrantLock lock = new ReentrantLock();
    private static final AtomicInteger specId = new AtomicInteger();

    private static final ThreadLocal specTape = new ThreadLocal<>();

    private final RestxRequest restxRequest;
    private final RestxResponse restxResponse;

    private final RestxSpecRecorder.RecordedSpec recordedSpec = new RestxSpecRecorder.RecordedSpec();
    private final Map givens = Maps.newLinkedHashMap();
    private final Set recorders;
    private final Set givenTapes = Sets.newLinkedHashSet();
    private final RestxSessionCookieFilter sessionFilter;
    private final RestxSpec.Storage storage;

    private RestxRequest recordingRequest;
    private RestxResponse recordingResponse;
    private int id;

    RestxSpecTape(RestxRequest restxRequest, RestxResponse restxResponse,
                  Set recorders, RestxSessionCookieFilter sessionFilter,
                  RestxSpec.StorageSettings storageSettings) {
        this.storage = RestxSpec.Storage.with(storageSettings);
        try {
            lock.tryLock(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException("in record mode only one request at a time can be processed, " +
                    "another request is currently being processed which is taking too much time");
        }

        this.recorders = recorders;
        this.restxRequest = restxRequest;
        this.restxResponse = restxResponse;
        this.sessionFilter = sessionFilter;
    }

    public RestxSpecRecorder.RecordedSpec close() {
        if (specTape.get() == RestxSpecTape.this) {
            specTape.remove();
        }

        // most of the time givenTapes should be empty by now, they are supposed to be closed when closing the response
        // but they may still be opened if:
        // - they failed to close when closing response
        // - response was not closed for some reason
        for (AutoCloseable givenTape : givenTapes) {
            try {
                givenTape.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        ThreadLocalMillisProvider.setCurrentMillisSystem();
        lock.unlock();

        return recordedSpec;
    }

    public RestxSpecTape doRecord(final Optional recordPath,
                                  final Optional recordTitle) throws IOException {
        specTape.set(this);

        for (RestxSpecRecorder.GivenRecorder recorder : recorders) {
            givenTapes.add(recorder.recordIn(givens));
        }

        DateTime now = DateTime.now();
        givens.put(GivenTime.class.getSimpleName() + "/now", new GivenTime(now));
        ThreadLocalMillisProvider.setCurrentMillisFixed(now.getMillis());
        recordedSpec.setRecordTime(now);
        Stopwatch stopwatch = Stopwatch.createStarted();
        System.out.print("RECORDING REQUEST...");
        final String method = restxRequest.getHttpMethod();
        final String path = (restxRequest.getRestxUri().length()>1)?restxRequest.getRestxUri().substring(1):restxRequest.getRestxUri(); // remove leading slash
        final ImmutableMap cookies =
                sessionFilter.toCookiesMap(sessionFilter.buildContextFromRequest(restxRequest));
        final byte[] requestBody = ByteStreams.toByteArray(restxRequest.getContentStream());
        System.out.println(" >> recorded request " + method + " " + path + " (" + requestBody.length + " bytes) -- " + stopwatch.stop());
        recordedSpec.setCapturedRequestSize(requestBody.length);

        id = specId.incrementAndGet();
        final String title = recordTitle.or(buildTitle(id, method, path));
        final String specPath = storage.buildPath(recordPath, title);

        recordingRequest = new RestxRequestWrapper(restxRequest) {
            @Override
            public InputStream getContentStream() throws IOException {
                return new ByteArrayInputStream(requestBody);
            }
        };
        recordingResponse = new RestxResponseWrapper(restxResponse) {
            private Stopwatch stopwatch = Stopwatch.createUnstarted();
            private ByteArrayOutputStream baos;
            private PrintWriter writer;
            private OutputStream realOS;
            public HttpStatus status = HttpStatus.OK;

            @Override
            public PrintWriter getWriter() throws IOException {
                if (writer == null) {
                    writer = super.getWriter();
                }

                return writer;
            }

            @Override
            public OutputStream getOutputStream() throws IOException {
                if (recordPath.isPresent()) {
                    super.setHeader("RestxSpecPath", specPath);
                }
                System.out.print("RECORDING RESPONSE...");
                stopwatch.start();
                realOS = super.getOutputStream();
                return baos = new ByteArrayOutputStream();
            }

            @Override
            public HttpStatus getStatus() {
                return status;
            }

            @Override
            public RestxResponse setStatus(HttpStatus i) {
                super.setStatus(i);
                status = i;
                return null;
            }

            @Override
            public void close() throws Exception {
                if (isClosed()) {
                    return;
                }

                if (writer != null) {
                    writer.flush();
                }

                System.out.println(" >> recorded response (" + baos.size() + " bytes) -- " + stopwatch.stop());
                if (realOS != null) {
                    ByteStreams.copy(ByteSource.wrap(baos.toByteArray()).openStream(), realOS);
                }
                super.close();

                Iterator iterator = givenTapes.iterator();
                while (iterator.hasNext()) {
                    AutoCloseable givenTape = iterator.next();
                    try {
                        givenTape.close();
                        iterator.remove();
                    } catch (Exception e) {
                        // will try again when closing the whole tape
                    }
                }
                ThreadLocalMillisProvider.setCurrentMillisSystem();

                RestxSpec restxSpec = new RestxSpec(
                        specPath, title,
                        ImmutableList.copyOf(givens.values()), ImmutableList.>of(
                        new WhenHttpRequest(method, path, cookies, new String(requestBody, Charsets.UTF_8),
                                new ThenHttpResponse(status.getCode(), baos.toString(Charsets.UTF_8.name())))));
                System.out.println("-----------------  RESTX SPEC  ---------------- \n"
                        + restxSpec + "\n"
                        + "------------------------------------------------"
                );
                recordedSpec.setId(id).setSpec(restxSpec).setMethod(method).setPath(path)
                        .setDuration(new Duration(recordedSpec.getRecordTime(), DateTime.now()))
                        .setCapturedResponseSize(baos.size());
            }
        };
        return this;
    }

    private String buildTitle(int id, String method, String path) {
        int endIndex = path.indexOf('?');
        endIndex = endIndex == -1 ? path.length() : endIndex;
        return String.format("%03d %s %s", id, method, path.substring(0, endIndex));
    }

    public RestxRequest getRecordingRequest() {
        return recordingRequest;
    }

    public RestxResponse getRecordingResponse() {
        return recordingResponse;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy