All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
restx.specs.RestxSpecTape Maven / Gradle / Ivy
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;
}
}