package com.xebialabs.restito.semantics;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.function.Function;
import com.xebialabs.restito.support.file.FileHelper;
import com.xebialabs.restito.support.resource.ResourceHelper;
import org.glassfish.grizzly.http.HttpResponsePacket;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.grizzly.http.util.HttpStatus;
/**
* Action is a modifier for Response
*
* @see org.glassfish.grizzly.http.server.Response
*/
public class Action implements Applicable {
private Function<Response, Response> function;
private Action(Function<Response, Response> function) {
this.function = function;
}
/**
* Perform the action with response
*/
@Override
public Response apply(Response r) {
return this.function.apply(r);
}
// Factory methods
/**
* Sets HTTP status 200 to response.
* Consider using {@link #ok()} as more concise and less ambiguous option.
* @see {@link #ok()}
*/
public static Action success() {
return status(HttpStatus.OK_200);
}
/**
* Sets HTTP status 200 to response
*/
public static Action ok() {
return status(HttpStatus.OK_200);
}
/**
* Sets HTTP status 204 to response
*/
public static Action noContent() {
return status(HttpStatus.NO_CONTENT_204);
}
/**
* Sets HTTP status to response
*/
public static Action status(final HttpStatus status) {
return new Action(input -> {
input.setStatus(status);
return input;
});
}
/**
* Writes content and content-type of resource file to response.
* Tries to detect content type based on file extension. If can not detect => content-type is not set.
* For now there are following bindings:
* <ul>
* <li>.xml => application/xml</li>
* <li>.json => application/xml</li>
* </ul>
*/
public static Action resourceContent(final String resourcePath) {
return new Action(input -> {
final HttpResponsePacket responsePacket = input.getResponse();
String encoding = responsePacket == null ? input.getCharacterEncoding() : responsePacket.getCharacterEncoding();
return resourceContent(resourcePath, encoding).apply(input);
});
}
/**
* Combines {@link #resourceContent(String)} and {@link #charset(String)}
*/
public static Action resourceContent(String resourcePath, String charset) {
URL resource = Action.class.getClassLoader().getResource(resourcePath);
if (resource == null) {
throw new IllegalArgumentException(String.format("Resource %s not found.", resourcePath));
}
Action charsetAction = charset == null ? noop() : charset(Charset.forName(charset));
return composite(resourceContent(resource), charsetAction);
}
/**
* Writes content using the specified encoding and content-type of resource file to response.
* Tries to detect content type based on file extension. If can not detect => content-type is not set.
* For now there are following bindings:
* <ul>
* <li>.xml => application/xml</li>
* <li>.json => application/xml</li>
* </ul>
*/
public static Action resourceContent(URL resourceUrl) {
try {
final byte[] bytes = ResourceHelper.getBytes(resourceUrl);
String fileExtension = FileHelper.getFileExtension(resourceUrl.getPath());
Action mainAction = bytesContent(bytes);
if (fileExtension.equalsIgnoreCase("xml")) {
mainAction = composite(contentType("application/xml"), mainAction);
} else if (fileExtension.equalsIgnoreCase("json")) {
mainAction = composite(contentType("application/json"), mainAction);
}
return mainAction;
} catch (IOException e) {
throw new RuntimeException("Can not read resource for restito stubbing.");
}
}
/**
* Combines {@link #resourceContent(java.net.URL)} and {@link #charset(java.nio.charset.Charset)}
*/
public static Action resourceContent(URL resourceUrl, Charset charset) {
final Action charsetAction = charset != null ? charset(charset) : Action.noop();
return composite(resourceContent(resourceUrl), charsetAction);
}
/**
* Writes bytes content to response
*/
public static Action bytesContent(final byte[] content) {
return new Action(response -> {
response.setContentLength(content.length);
try {
response.getOutputStream().write(content);
} catch (IOException e) {
throw new RuntimeException("Can not write resource content for restito stubbing.");
}
return response;
});
}
/**
* Writes string content to response
*/
public static Action stringContent(final String content) {
return bytesContent(content.getBytes());
}
/**
* Sets key-value header on response
*/
public static Action header(final String key, final String value) {
return new Action(input -> {
input.setHeader(key, value);
return input;
});
}
/**
* Sets content type to the response
*/
public static Action contentType(final String contentType) {
return new Action(r -> {
r.setContentType(contentType);
return r;
});
}
/**
* Sets charset of the response (must come before stringContent/resourceContent Action)
*/
public static Action charset(final String charset) {
return new Action(r -> {
r.setCharacterEncoding(charset);
return r;
});
}
/**
* Sets charset of the response (must come before stringContent/resourceContent Action)
*/
public static Action charset(final Charset charset) {
return charset(charset.name());
}
/**
* Returns unauthorized response with default realm name
*/
public static Action unauthorized() {
return unauthorized("Restito realm");
}
/**
* Returns unauthorized response
*/
public static Action unauthorized(final String realm) {
return new Action(r -> {
r.addHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
r.setStatus(HttpStatus.UNAUTHORIZED_401);
return r;
});
}
/**
* Perform set of custom actions on response
*/
public static Action custom(Function<Response, Response> f) {
return new Action(f);
}
/**
* Creates a composite action which contains all passed actions and
* executes them in the same order.
*/
public static Action composite(final Applicable... actions) {
return new Action(input -> {
for (Applicable action : actions) {
action.apply(input);
}
return input;
});
}
/**
* Creates a composite action which contains all passed actions and
* executes them in the same order.
*/
public static Action composite(final Collection<Applicable> applicables) {
return new Action(input -> {
for (Applicable action : applicables) {
action.apply(input);
}
return input;
});
}
/**
* Creates a composite action which contains all passed actions and
* executes them in the same order.
*/
public static Action composite(final Action... actions) {
return new Action(input -> {
for (Applicable action : actions) {
action.apply(input);
}
return input;
});
}
/**
* Doing nothing. To be used in DSLs for nicer syntax.
*/
public static Action noop() {
return new Action(input -> input);
}
/**
* Sleeps so many milliseconds, emulating slow requests.
*/
public static Action delay(final Integer delay) {
return new Action(input -> {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return input;
});
}
}