/*
* Copyright (C) 2014 Civilian Framework.
*
* Licensed under the Civilian License (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.civilian-framework.org/license.txt
*
* 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.civilian.context.test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.Cookie;
import org.civilian.Application;
import org.civilian.Controller;
import org.civilian.Request;
import org.civilian.Resource;
import org.civilian.content.ContentSerializer;
import org.civilian.content.ContentType;
import org.civilian.content.ContentTypeList;
import org.civilian.internal.AbstractRequest;
import org.civilian.internal.ParamList;
import org.civilian.request.AsyncContext;
import org.civilian.request.CookieList;
import org.civilian.request.Upload;
import org.civilian.resource.PathParam;
import org.civilian.resource.Url;
import org.civilian.util.Check;
import org.civilian.util.IoUtil;
import org.civilian.util.Iterators;
/**
* TestRequest is a {@link Request} implementation to be used in a test environment.
* Create a TestRequest for an application, set its properties, {@link #run() run}
* the request, and then evaluate the returned response.
*/
public class TestRequest extends AbstractRequest
{
/**
* Creates a TestRequest.
*/
public TestRequest(Application application, String relativePath)
{
super(application, relativePath);
setResponse(testResponse_ = new TestResponse(this));
}
/**
* Creates a TestRequest.
*/
public TestRequest(Application application)
{
this(application, (String)null);
}
/**
* Creates a TestRequest.
*/
public TestRequest()
{
this(createTestApp());
}
private static Application createTestApp()
{
TestApp app = new TestApp();
app.init();
return app;
}
/**
* Creates a TestRequest which copies the settings from another request.
*/
public TestRequest(TestRequest request)
{
super(request);
setResponse(testResponse_ = new TestResponse(this, request.getTestResponse()));
method_ = request.method_;
cookies_ = request.cookies_;
attributes_ = request.attributes_;
parameters_ = request.parameters_;
uploads_ = request.uploads_;
uploadError_ = request.uploadError_;
contentEncoding_ = request.contentEncoding_;
contentBytes_ = request.contentBytes_;
contentString_ = request.contentString_;
security_ = request.security_;
serverInfo_ = request.serverInfo_;
remoteInfo_ = request.remoteInfo_;
localInfo_ = request.localInfo_;
headers_ = request.headers_;
session_ = request.session_;
acceptedLocale_ = request.acceptedLocale_;
}
/**
* Returns the TestResponse object associated with the TestRequest.
*/
public TestResponse getTestResponse()
{
return testResponse_;
}
//-----------------------------
// run
//-----------------------------
/**
* Invokes {@link Application#process(Request)} with this request.
* Before calling process(), the TestResponse associated with this request
* is cleared first and the content of this request is reset}.
* @return the TestResponse
*/
public TestResponse run() throws Exception
{
testResponse_.clear();
resetAsyncContext();
resetContentInput();
getApplication().process(this);
getResponse().flushBuffer();
return testResponse_;
}
//-----------------------------
// method
//-----------------------------
/**
* Returns the request method.
*/
@Override public String getMethod()
{
return method_;
}
/**
* Sets the request method.
* @return this
*/
public TestRequest setMethod(String method)
{
method_ = Check.notNull(method, "method");
return this;
}
//-----------------------------
// path
//-----------------------------
/**
* Sets the path of the request relative to the application path.
* @param path the relative request path
* @return this
*/
public TestRequest setPath(String path)
{
setRelativePath(path);
return this;
}
/**
* Sets the path of the request relative to the application path.
* @param resource the resource which defines the path
* @param pathParams the path parameters of the resource
* @return this
*/
public TestRequest setPath(Resource resource, Object... pathParams)
{
Url url = new Url(this, resource);
url.setPathParams(pathParams);
return setPath(url);
}
/**
* Sets the path of the request relative to the application path.
* @param controllerClass a controller class. The resource associated with the controller
* is used to define the path
* @param pathParams the path parameters of the resource
* @return this
*/
public TestRequest setPath(Class<? extends Controller> controllerClass, Object... pathParams)
{
Url url = new Url(this, controllerClass);
url.setPathParams(pathParams);
return setPath(url);
}
/**
* Sets the relative path of the request.
* @param url a Url. Resource, path parameters and query parameters are copied
* from the resource to this request
* @return this
*/
public TestRequest setPath(Url url)
{
setRelativePath(url.toQuerylessString());
setResource(url.getResource());
clearPathParams();
for (int i=0; i<url.getPathParamCount(); i++)
setPathParam(url, i);
clearParameters();
for (int i=0; i<url.getQueryParamCount(); i++)
{
Url.QueryParam qp = url.getQueryParam(i);
getParameterList().add(qp.getName(), qp.getValue());
}
return this;
}
@SuppressWarnings("unchecked")
private <T> void setPathParam(Url url, int i)
{
setPathParam((PathParam<T>)url.getPathParamDef(i), (T)url.getPathParam(i));
}
/**
* Returns the path string.
*/
@Override public String getOriginalPath()
{
return getPath().print();
}
//----------------------------
// attributes
//----------------------------
/**
* Returns an attribute.
*/
@Override public Object getAttribute(String name)
{
return attributes_ != null ? attributes_.get(name) : null;
}
/**
* Returns the attribute names.
*/
@Override public Iterator<String> getAttributeNames()
{
return attributes_ != null ? attributes_.keySet().iterator() : Iterators.<String>empty();
}
/**
* Allows to set an attribute.
*/
@Override public void setAttribute(String name, Object value)
{
if (attributes_ == null)
attributes_ = new HashMap<>();
attributes_.put(name, value);
}
//----------------------------
// path parameters
//----------------------------
/**
* Clears the path parameters.
*/
@Override public void clearPathParams()
{
// call protected method
super.clearPathParams();
}
//----------------------------
// cookies
//----------------------------
/**
* Returns the CookieList.
*/
@Override public CookieList getCookies()
{
if (cookies_ == null)
cookies_ = new CookieList();
return cookies_;
}
/**
* Allows to set the cookies.
* @return this
*/
public TestRequest setCookies(CookieList cookies)
{
cookies_ = cookies;
return this;
}
/**
* Allows to set the cookies.
* @return this
*/
public TestRequest setCookies(Cookie... cookies)
{
return setCookies(new CookieList(cookies));
}
//----------------------------
// parameters
//----------------------------
/**
* Returns a request parameter.
*/
@Override public String getParameter(String name)
{
return parameters_.get(name);
}
/**
* Returns all values of a request parameter.
*/
@Override public String[] getParameters(String name)
{
return parameters_.getAll(name);
}
/**
* Returns the names of all request parameters.
*/
@Override public Iterator<String> getParameterNames()
{
return parameters_.iterator();
}
/**
* Returns a map of all request parameters.
*/
@Override public Map<String, String[]> getParameterMap()
{
return parameters_.getMap();
}
/**
* Returns a ParamList containing the request parameters.
* The ParamList allows you to add parameters.
*/
public ParamList getParameterList()
{
return parameters_;
}
/**
* Sets a request parameter.
*/
public TestRequest setParameter(String name, String value)
{
parameters_.set(name, value);
return this;
}
/**
* Clears the parameters.
*/
public void clearParameters()
{
parameters_.clear();;
}
//----------------------------
// uploads
//----------------------------
/**
* Returns if the request has uploads.
*/
@Override public boolean hasUploads()
{
return uploads_ != null && uploads_.size() > 0;
}
/**
* Returns a upload.
*/
@Override public Upload getUpload(String name)
{
return uploads_ != null ? uploads_.get(name) : null;
}
/**
* Returns all uploads for the given name.
*/
@Override public Upload[] getUploads(String name)
{
Upload upload = getUpload(name);
return upload != null ? new Upload[] { upload } : new Upload[0];
}
/**
* Returns the names of all uploads.
*/
@Override public Iterator<String> getUploadNames()
{
return uploads_ != null ? uploads_.keySet().iterator() : Iterators.<String>empty();
}
/**
* Allows you to add an upload.
* @return this
*/
public TestRequest addUpload(Upload upload)
{
if (uploads_ == null)
uploads_ = new HashMap<>();
uploads_.put(upload.getName(), upload);
return this;
}
/**
* Adds and returns a TestUpload object. Use setters on
* the returned object to set further details.
*/
public TestUpload addUpload(String name, String fileName)
{
TestUpload upload = new TestUpload(name, fileName);
addUpload(upload);
return upload;
}
/**
* Allows you to clear the uploads.
* @return this
*/
public TestRequest clearUploads()
{
uploads_ = null;
return this;
}
/**
* Returns any upload error.
*/
@Override public Exception getUploadError()
{
return uploadError_;
}
/**
* Allows you to set the upload error.
* @return this
*/
public TestRequest setUploadError(Exception error)
{
uploadError_ = error;
return this;
}
//----------------------------
// content
//----------------------------
/**
* Returns the content encoding.
*/
@Override public String getContentEncoding()
{
return contentEncoding_;
}
/**
* Sets the encoding.
*/
@Override public void setContentEncoding(String encoding)
{
contentEncoding_ = encoding;
}
/**
* Returns the content length.
*/
@Override public long getContentLength()
{
return getContentBytes().length;
}
/**
* Returns the content-type.
*/
@Override protected ContentType getContentTypeImpl()
{
return null;
}
/**
* Returns an input stream for the content.
*/
@Override protected InputStream getContentStreamImpl() throws IOException
{
return new ByteArrayInputStream(getContentBytes());
}
private byte[] getContentBytes()
{
if (contentBytes_ == null)
{
if (contentString_ == null)
contentBytes_ = EMPTY_BYTES;
else
{
String encoding = contentEncoding_ != null ? contentEncoding_ : getApplication().getEncoding();
ByteArrayOutputStream out = new ByteArrayOutputStream();
try
{
OutputStreamWriter writer = new OutputStreamWriter(out, encoding);
IoUtil.copy(new StringReader(contentString_), writer);
writer.flush();
}
catch (Exception e)
{
throw new IllegalStateException("unexpected", e);
}
contentBytes_ = out.toByteArray();
}
}
return contentBytes_;
}
/**
* Returns a reader for the content.
*/
@Override protected Reader getContentReaderImpl() throws IOException
{
if (contentString_ != null)
return new StringReader(contentString_);
else
{
String encoding = contentEncoding_ != null ? contentEncoding_ : getApplication().getEncoding();
return new InputStreamReader(new ByteArrayInputStream(getContentBytes()), encoding);
}
}
/**
* Allows you to set the content.
* @return this
*/
public TestRequest setContent(byte[] content)
{
contentBytes_ = content != null ? content : new byte[0];
resetContentInput();
return this;
}
/**
* Allows you to set the content.
* @param content the content. If it is null, the content will be set empty.
* If it is a byte array or a string the content will be set to that value.
* For any other object, the object will be serialized using the current content-type.
* @return this
*/
public TestRequest setContent(Object content) throws Exception
{
resetContentInput();
if (content != null)
{
if (EMPTY_BYTES.getClass() == content.getClass())
contentBytes_ = (byte[])content;
else if (content instanceof String)
contentString_ = (String)content;
else
{
ContentType contentType = getContentType();
if (contentType == null)
throw new IllegalStateException("no content-type set");
ContentSerializer serializer = getApplication().getContentSerializer(contentType);
if (serializer == null)
throw new IllegalStateException("don't know how to write content with content type '" + contentType + "'");
contentString_ = serializer.write(content);
}
}
return this;
}
/**
* Resets content Reader or InputStream.
* The request content can now be read again.
*/
@Override public void resetContentInput()
{
super.resetContentInput();
}
//----------------------------
// headers
//----------------------------
/**
* Returns the request headers.
*/
@Override public ParamList getHeaders()
{
return headers_;
}
public TestRequest setAcceptedLocale(Locale locale)
{
acceptedLocale_ = Check.notNull(locale, "locale");
return this;
}
@Override public Locale getAcceptedLocale()
{
if (acceptedLocale_ == null)
acceptedLocale_ = getApplication().getLocaleServices().getDefaultLocale();
return acceptedLocale_;
}
@Override public Iterator<Locale> getAcceptedLocales()
{
return Iterators.forValue(getAcceptedLocale());
}
/**
* Allows you to set the preferred content types.
* @return this
*/
public TestRequest setAcceptedContentTypes(ContentTypeList contentTypes)
{
acceptedContentTypes_ = Check.notNull(contentTypes, "contentTypes");
getHeaders().set("Accept", acceptedContentTypes_.toString());
return this;
}
public TestRequest setAcceptedContentTypes(ContentType... contentTypes)
{
setAcceptedContentTypes(new ContentTypeList(contentTypes));
return this;
}
public AcceptCtBuilder setAcceptedContentTypes()
{
return new AcceptCtBuilder();
}
public class AcceptCtBuilder
{
public AcceptCtBuilder()
{
setAcceptedContentTypes(ContentTypeList.EMPTY);
}
public AcceptCtBuilder add(ContentType... types)
{
return add(1.0, types);
}
public AcceptCtBuilder add(double quality, ContentType... types)
{
for (ContentType type : types)
{
type = type.withQuality(quality);
types_.add(type);
}
setAcceptedContentTypes(new ContentTypeList(types_));
return this;
}
private ArrayList<ContentType> types_ = new ArrayList<>();
}
//----------------------------
// async
//----------------------------
@Override public boolean isAsyncSupported()
{
return false;
}
@Override protected AsyncContext createAsyncContext()
{
return new TestAsyncContext(this);
}
//----------------------------
// security
//----------------------------
@Override public TestSession getSession(boolean create)
{
if ((session_ == null) && create)
session_ = new TestSession();
return session_;
}
public TestRequest setSession(TestSession session)
{
session_ = session;
return this;
}
@Override public TestSecurity getSecurity()
{
if (security_ == null)
security_ = new TestSecurity();
return security_;
}
public TestRequest setSecurity(TestSecurity security)
{
security_ = security;
return this;
}
//----------------------------
// server info
//----------------------------
@Override public TestServerInfo getServerInfo()
{
if (serverInfo_ == null)
serverInfo_ = new TestServerInfo();
return serverInfo_;
}
public TestRequest setServerInfo(TestServerInfo info)
{
serverInfo_ = info;
return this;
}
//----------------------------
// remote info
//----------------------------
@Override public TestRemoteInfo getRemoteInfo()
{
if (remoteInfo_ == null)
remoteInfo_ = new TestRemoteInfo();
return remoteInfo_;
}
public TestRequest setRemoteInfo(TestRemoteInfo info)
{
remoteInfo_ = info;
return this;
}
//----------------------------
// local info
//----------------------------
@Override public TestLocalInfo getLocalInfo()
{
if (localInfo_ == null)
localInfo_ = new TestLocalInfo();
return localInfo_;
}
public TestRequest setLocalInfo(TestLocalInfo info)
{
localInfo_ = info;
return this;
}
/**
* Returns null.
*/
@Override public <T> T unwrap(Class<T> implClass)
{
return null;
}
private String method_ = Request.Method.GET;
private CookieList cookies_;
private HashMap<String, Object> attributes_;
private ParamList parameters_ = new ParamList();
private HashMap<String, Upload> uploads_;
private Exception uploadError_;
private String contentEncoding_ = "UTF-8";
private byte[] contentBytes_;
private String contentString_;
private TestSecurity security_;
private TestServerInfo serverInfo_;
private TestRemoteInfo remoteInfo_;
private TestLocalInfo localInfo_;
private ParamList headers_ = new ParamList(true);
private TestSession session_;
private Locale acceptedLocale_ = Locale.getDefault();
private TestResponse testResponse_;
private static final byte[] EMPTY_BYTES = new byte[0];
}