/*
* Copyright 2013 Matt Sicker and Contributors
*
* 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 atg.tools.dynunit.droplet;
import atg.droplet.TagConverter;
import atg.nucleus.Nucleus;
import atg.servlet.DynamoHttpServletRequest;
import atg.servlet.DynamoHttpServletResponse;
import atg.servlet.ServletUtil;
import atg.tools.dynunit.servlet.ServletTestUtils;
import atg.tools.dynunit.servlet.ServletTestUtils.ServiceParameterCallback;
import atg.tools.dynunit.servlet.ServletTestUtils.TestingDynamoHttpServletRequest;
import atg.tools.dynunit.servlet.ServletTestUtils.TestingDynamoHttpServletResponse;
import org.jetbrains.annotations.Nullable;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* A utility class for invoking droplets and capturing the results. This class
* provides invokeDroplet methods that return a DropletResult, which captures
* which OPARAMs were rendered and what the stack frame contained at that time.
* <p/>
* If the forms of invokeDroplet() which have no request and response parameters
* are invoked, then the DropletInvoker instance's request and response
* properties will be used (auto-creating a request and response if needed).
* <p/>
* The request and response wil be re-used on later invokeDroplet() invocations
* with explicit request and response parameters. Use resetRequestResponse() to
* clear the existing request and response.
* <p/>
* DropletInvoker provides protected methods as extension points. For example,
* if you need to save additional information in a RenderedOutputParameter, you
* can override createRenderedOutputParameter to create a sub-class.
* <p/>
* Note that you can also set your own Servlets on the request, and they will be
* invoked as normal OPARAMETERs. This does not prevent RenderedOutputParameters
* from being created for that invocation unless an exception is thrown.
* <p/>
* You can also set oparamsExistByDefault to control whether serviceParameter
* and serviceLocalParameter return true even if no Servlet is found for the
* specified OPARM name. Additional, you can use setOparamExistsOverride() to
* control what the request returns for rendering an OPARAM with the given name.
* <p/>
* Created: October 19 2009
*
* @author Charles Morehead
* @version $Id:
* //test/UnitTests/base/main/src/Java/atg/droplet/DropletInvoker.java
* #5 $
*/
public class DropletInvoker {
// -------------------------------------
// Class version string
/**
* Class version string from source code control system.
*/
public static final String CLASS_VERSION = "$Id: //test/UnitTests/base/main/src/Java/atg/droplet/DropletInvoker.java#5 $";
// -------------------------------------
// Constants
// -------------------------------------
// Member variables
private final Map<String, Boolean> mOparamExistsOverrideMap = new HashMap<String, Boolean>();
private ServletTestUtils mServletTestUtils;
@Nullable
private TestingDynamoHttpServletRequest mRequest;
private TestingDynamoHttpServletResponse mResponse;
private String mSessionId;
private final Nucleus mNucleus;
// -------------------------------------
// Properties
// -------------------------------------
// property: servletTestUtils
/**
* Return out instance of servletTestUtils. Invokes createServletTestUtils to
* create one if none exists.
*
* @return the servletTestUtils instance used to create requests/responses.
*/
public ServletTestUtils getServletTestUtils() {
if (mServletTestUtils == null) {
mServletTestUtils = createServletTestUtils();
}
return mServletTestUtils;
}
// ---------------------------------------------------------------------------
// readonly property: nucleus
/**
* Return the nucleus property.
*
* @return our Nucleus instance
*/
public Nucleus getNucleus() {
return mNucleus;
}
// -------------------------------------
// property: request
/**
* Return the TestingDynamoHttpServletRequest, creating one, if needed. This
* is the response used by invokeDroplet() if no existing
* TestingDynamoHttpServletRequest is given.
*
* @return the request used for invokeDroplet if no explicit request and
* response are given.
*/
public TestingDynamoHttpServletRequest getRequest() {
if (mRequest == null) {
mRequest = createRequest();
}
return mRequest;
}
/**
* Return the TestingDynamoHttpServletRequest, creating one, if needed. This
* is the response used by invokeDroplet() if no existing request and response
* are given.
*
* @return the response used for invokeDroplet if no explicit request and
* response are given.
*/
public TestingDynamoHttpServletResponse getResponse() {
if (mResponse == null) {
mResponse = (TestingDynamoHttpServletResponse) (getRequest().getResponse());
}
return mResponse;
}
/**
* Reset the request and response.
*/
public TestingDynamoHttpServletRequest resetRequestResponse() {
mRequest = null;
mResponse = null;
return getRequest();
}
// -------------------------------------
// property: sessionId
/**
* Sets the session ID property. The session ID will be used for created our
* request property, if a request is needed..
*
* @param pSessionId
* the new value for session ID
*/
public void setSessionId(String pSessionId) {
mSessionId = pSessionId;
}
/**
* Returns the session ID property. The session ID will be used for created
* our request property, if a request is needed.
*
* @return the session ID used for creating our request property's request.
*/
public String getSessionId() {
return mSessionId;
}
// -------------------------------------
// property: oparamsExistByDefault
private boolean mOparamsExistByDefault = true;
/**
* Sets whether OPARAMs exist by default.
*
* @param pOparamsExistByDefault
* true if serviceParameter() and serviceLocalParameter() will
* return
* true by default, even if no parameter by that name was found.
*/
public void setOparamsExistByDefault(boolean pOparamsExistByDefault) {
mOparamsExistByDefault = pOparamsExistByDefault;
}
/**
* Returns property oparamsExistByDefault
*
* @return true if serviceParameter() and serviceLocalParameter() will return
* true by default, even if no parameter by that name was found.
*/
public boolean getOparamsExistByDefault() {
return mOparamsExistByDefault;
}
// -------------------------------------
// oparamExistsOverrides
/**
* Add an override for whether the given parameter exists.
*/
public void clearOparamExistsOverrides() {
mOparamExistsOverrideMap.clear();
}
/**
* Add an override for whether the given parameter exists.
*
* @param pName
* the name of the OPARAM to say exists (or doesn't).
* @param pValue
* TRUE means say that the specified OPARAM exists, FALSE means say
* the the OPARAM does not exist.
*/
public void setOparamExistsOverride(String pName, Boolean pValue) {
if (pValue == null) {
mOparamExistsOverrideMap.remove(pName);
}
else {
mOparamExistsOverrideMap.put(pName, pValue);
}
}
// -------------------------------------------------------
/**
* Create a new DropletInvoker that will use the specified Nucleus.
*/
public DropletInvoker(Nucleus pNucleus) {
mNucleus = pNucleus;
}
/**
* Invoke the specified droplet, using the the DropletInvoker instance's
* request and response.
*
* @param pDropletName
* the Nucleus name of the droplet to invoke.
* @param pAdditionalParameters
* additional parameters to set on the request. These parameters
* are
* set inside the droplet-specific parameter stack frame.
*
* @return a droplet result object representing the OPARAMs rendered.
*
* @throws ServletException
* if an error occurs
* @throws IOException
* if an error occurs
*/
public DropletResult invokeDroplet(String pDropletName,
Map<String, Object> pAdditionalParameters)
throws ServletException, IOException {
return invokeDroplet(
pDropletName, pAdditionalParameters, getRequest(), getResponse()
);
}
/**
* Invoke the specified droplet, using the the DropletInvoker instance's
* request and response.
*
* @param pDropletName
* the Nucleus name of the droplet to invoke.
*
* @return a droplet result object representing the OPARAMs rendered.
*
* @throws ServletException
* if an error occurs
* @throws IOException
* if an error occurs
*/
public DropletResult invokeDroplet(String pDropletName)
throws ServletException, IOException {
return invokeDroplet(pDropletName, null, getRequest(), getResponse());
}
/**
* Invoke the specified droplet, using the specified request and response.
* Note that the specified request and response may be the invoker's request
* and response, or may be externally provided.
*
* @param pDropletName
* the Nucleus name of the droplet to invoke.
* @param pAdditionalParameters
* additional parameters to set on the request. These parameters
* are
* set inside the droplet-specific parameter stack frame.
* @param pRequest
* the request to use for droplet invocation.
* @param pResponse
* the response to use for droplet invocation.
*
* @return a droplet result object representing the OPARAMs rendered.
*
* @throws ServletException
* if an error occurs
* @throws IOException
* if an error occurs
*/
public DropletResult invokeDroplet(String pDropletName,
Map<String, Object> pAdditionalParameters,
TestingDynamoHttpServletRequest pRequest,
TestingDynamoHttpServletResponse pResponse)
throws ServletException, IOException {
DynamoHttpServletRequest requestRestore = ServletUtil.setCurrentRequest(pRequest);
try {
Servlet droplet = (Servlet) getRequest().resolveName(pDropletName);
if (droplet == null) {
throw new IllegalArgumentException(
"Droplet " + pDropletName + " not found."
);
}
return invokeDroplet(droplet, pAdditionalParameters, pRequest, pResponse);
} finally {
ServletUtil.setCurrentRequest(requestRestore);
}
}
/**
* Invoke the specified droplet, using the specified request and response.
* Note that the specified request and response may be the invoker's request
* and response, or may be externally provided.
*
* @param pDropletName
* the Nucleus name of the droplet to invoke.
* @param pRequest
* the request to use for droplet invocation.
* @param pResponse
* the response to use for droplet invocation.
*
* @return a droplet result object representing the OPARAMs rendered.
*
* @throws ServletException
* if an error occurs
* @throws IOException
* if an error occurs
*/
public DropletResult invokeDroplet(String pDropletName,
TestingDynamoHttpServletRequest pRequest,
TestingDynamoHttpServletResponse pResponse)
throws ServletException, IOException {
return invokeDroplet(pDropletName, null, pRequest, pResponse);
}
/**
* Invoke the specified droplet, using the specified request and response.
* Note that the specified request and response may be the invoker's request
* and response, or may be externally provided.
*
* @param pDroplet
* the droplet to invoke.
* @param pAdditionalParameters
* additional parameters to set on the request. These parameters
* are
* set inside the droplet-specific parameter stack frame.
* @param pRequest
* the request to use for droplet invocation.
* @param pResponse
* the response to use for droplet invocation.
*
* @return a droplet result object representing the OPARAMs rendered.
*
* @throws ServletException
* if an error occurs
* @throws IOException
* if an error occurs
*/
public DropletResult invokeDroplet(Servlet pDroplet,
Map<String, Object> pAdditionalParameters,
TestingDynamoHttpServletRequest pRequest,
TestingDynamoHttpServletResponse pResponse)
throws ServletException, IOException {
DropletResult result = createDropletResult(pDroplet, pRequest, pResponse);
DynamoHttpServletRequest requestRestore = ServletUtil.setCurrentRequest(pRequest);
ServiceParameterCallback callbackRestore = pRequest.setServiceParameterCallback(
createServiceParameterCallback(result)
);
try {
pRequest.pushFrame();
if (pAdditionalParameters != null) {
for (Map.Entry<String, Object> entryCur : pAdditionalParameters.entrySet()) {
pRequest.setParameter(entryCur.getKey(), entryCur.getValue());
}
}
pDroplet.service(pRequest, pResponse);
} finally {
pRequest.setServiceParameterCallback(callbackRestore);
pRequest.popFrame();
ServletUtil.setCurrentRequest(requestRestore);
}
return result;
}
/**
* Create a new DropletResult. This method exists so can be overridden is
* subclasses, as needed.
*
* @param pDroplet
* the droplet whose invocation our DropletResult will represent
* @param pRequest
* the request used for the droplet invocation
* @param pResponse
* the response used for the droplet invocation
*
* @return the result representation.
*/
protected DropletResult createDropletResult(Servlet pDroplet,
DynamoHttpServletRequest pRequest,
DynamoHttpServletResponse pResponse) {
return new DropletResult(pDroplet, pRequest, pResponse);
}
/**
* Create a new ServiceParameterCallback.
*
* @param pResult
* the DropletResult whose rendered parameters the
* ServiceParameterCallback should add to.
*
* @return a newly created ServiceParameterCallback
*/
protected ServiceParameterCallback createServiceParameterCallback(DropletResult pResult) {
return new ServiceParameterCallbackImpl(pResult);
}
/**
* Return newly created ServletTestUtils. Provided so that sub-classes can
* override.
*
* @return a newly created ServletTestUtils.
*/
protected ServletTestUtils createServletTestUtils() {
return new ServletTestUtils();
}
/**
* Return newly created TestingDynamoHttpServletResponse. This method is used
* to create a request for our request property to be used if the form of
* invokeDroplet sans request/response is invoked.
*
* @return a new request
*/
protected TestingDynamoHttpServletRequest createRequest() {
TestingDynamoHttpServletRequest request = getServletTestUtils().createDynamoHttpServletRequestForSession(
getNucleus(), createValueParametersMapForNewRequest(), 1024, "GET", getSessionId()
);
if (request.getResponse() instanceof TestingDynamoHttpServletResponse) {
TestingDynamoHttpServletResponse testResponse = ((TestingDynamoHttpServletResponse) request
.getResponse());
testResponse.setBlockDispatches(true);
testResponse.setRecordDispatches(true);
}
return request;
}
/**
* Called to create a RenderedOutputParameter. Can be overridden to create a
* RenderedOutputParameter subclass to record additional information.
*
* @param pName
* the name of the OPARAM being request
* @param pRequest
* the request used to render the OPARAM
* @param pResponse
* the response used to render the OPARAM
*
* @return a newly created RenderedOutputParameter (or subclass)
*/
protected RenderedOutputParameter createRenderedOutputParameter(String pName,
DynamoHttpServletRequest pRequest,
DynamoHttpServletResponse pResponse) {
return new RenderedOutputParameter(pName, pRequest, pResponse);
}
/**
* Create a value parameters map for the new request. For the time being, just
* returns an empty Map.
*
* @return a Map for a new request.
*/
protected Map<String, Object> createValueParametersMapForNewRequest() {
return new HashMap<String, Object>();
}
// -------------------------------------
/**
* A representation of a rendered OParam. Takes a snapshot of of the
* FrameParameters.
*/
public static class RenderedOutputParameter {
final Map<String, Object> mFrameParameters;
final String mName;
/**
* Created a representation of a rendered OutputParameter.
*
* @param pName
* the name out the OPARAM
* @param pRequest
* the request at the time the OPARAM should be rendered.
* @param pResponse
* the response at the time the OPARAM should be rendered.
*/
public RenderedOutputParameter(String pName,
DynamoHttpServletRequest pRequest,
DynamoHttpServletResponse pResponse) {
mName = pName;
// mFrameParameters = new HashMap(pRequest.getMapForCurrentFrame());
mFrameParameters = new HashMap<String, Object>();
// because of a bug with entrySet in 9.0, we go through keys
// and copy manually, here.
Map mapFrame = pRequest.getMapForCurrentFrame();
for (Object o : mapFrame.keySet()) {
String strKey = (String) o;
mFrameParameters.put(strKey, mapFrame.get(strKey));
}
}
/**
* Return the name of the rendered OPARAM
*
* @return the name of OPARAM whose rendering this object represents.
*/
public String getName() {
return mName;
}
/**
* Return the map of frame parameters.
*
* @return the map of the parameters defined in the stack frame when the
* OPARAM was rendered.
*/
public Map<String, Object> getFrameParameters() {
return mFrameParameters;
}
/**
* Get the specified frame parameter from the parameter dictionary.
*
* @param pName
* the name of the parameter to return
*
* @return the parameter value, or null if no parameter by that name was
* found.
*/
public Object getFrameParameter(String pName) {
return mFrameParameters.get(pName);
}
public String toString() {
return getClass().getName()
+ "(name="
+ getName()
+ ", frameParameters="
+ getFrameParameters()
+ ")";
}
} // end inner-class RenderedOutputParameter
/**
* Represents the result of a droplet invocation. Contains properties
* representing the droplet, request and response. Also tracks which OPARAMs
* were rendered during the course of the droplet.
*/
public class DropletResult {
final Servlet mDroplet;
final DynamoHttpServletRequest mRequest;
final DynamoHttpServletResponse mResponse;
final List<RenderedOutputParameter> mRenderedParameters = new ArrayList<RenderedOutputParameter>();
final Map<String, List<RenderedOutputParameter>> mNameToRenderedParameters = new HashMap<String, List<RenderedOutputParameter>>();
/**
* Create a new DropletResult.
*
* @param pDroplet
* the droplet being invoke
* @param pRequest
* the request used for the droplet invocation
* @param pResponse
* the response used for the droplet invocation
*/
public DropletResult(Servlet pDroplet,
DynamoHttpServletRequest pRequest,
DynamoHttpServletResponse pResponse) {
mDroplet = pDroplet;
mRequest = pRequest;
mResponse = pResponse;
}
/**
* Debugging toString.
*/
public String toString() {
StringBuilder strbuf = new StringBuilder(getClass().getName());
strbuf.append("(droplet=").append(mDroplet);
strbuf.append(", renderedParameters=").append(mRenderedParameters);
strbuf.append(", nameToRenderedParameters=").append(
mNameToRenderedParameters
);
strbuf.append(")");
return strbuf.toString();
}
/**
* Return the droplet that was invoked.
*
* @return the droplet that was invoked.
*/
public Servlet getDroplet() {
return mDroplet;
}
/**
* Add a rendered OPARAM to our list and map of OPARAMs.
*
* @param pParameter
* the parameter to add.
*/
public void addRenderedParameter(RenderedOutputParameter pParameter) {
mRenderedParameters.add(pParameter);
List<RenderedOutputParameter> listParams = mNameToRenderedParameters.get(pParameter.getName());
if (listParams == null) {
listParams = new ArrayList<RenderedOutputParameter>();
mNameToRenderedParameters.put(pParameter.getName(), listParams);
}
listParams.add(pParameter);
}
/**
* Return the list of all rendered OPARAMs, in order.
*/
public List<RenderedOutputParameter> getRenderedOutputParameters() {
return mRenderedParameters;
}
/**
* Return the list of all rendered OPARAMs with the specified name.
*
* @param pName
* the name of the oparam to return.
*
* @return the list of rendered OPARAMS with the specified name, or null if
* none exist.
*/
public List<RenderedOutputParameter> getRenderedOutputParametersByName(String pName) {
return mNameToRenderedParameters.get(pName);
}
/**
* Return the OPARAM with the specified name.
*
* @param pName
* the name of the OPARAM to return.
*
* @return the list of rendered OPARAMS with the specified name, or null if
* none exist.
*
* @throws IllegalStateException
* if multiple OPARAMS with the given name were rendered.
*/
public RenderedOutputParameter getRenderedOutputParameter(String pName) {
return getRenderedOutputParameter(pName, true);
}
/**
* Return the first rendered OPARAM with the specified name.
*
* @param pName
* the name of the OPARAM to return.
* @param pEnforceSingle
* if there is more than one OPARAM with the specified name, throw
* an IllegalStateException.
*
* @return the list of rendered OPARAMS with the specified name, or null if
* none exist.
*
* @throws IllegalStateException
* if multiple OPARAMS with the given name were rendered and
* pEnforceSingle is true.
*/
public RenderedOutputParameter getRenderedOutputParameter(String pName,
boolean pEnforceSingle) {
List<RenderedOutputParameter> listParams = getRenderedOutputParametersByName(pName);
RenderedOutputParameter paramResult = null;
if ((listParams != null) && !listParams.isEmpty()) {
if (pEnforceSingle && (listParams.size() > 1)) {
throw new IllegalStateException(
"More than one rendered OPARAM found for " + pName
);
}
paramResult = listParams.get(0);
}
return paramResult;
}
/**
* Return the rendered OPARAM with the specified name and index.
*
* @param pName
* the name of the OPARAM to return.
* @param pIndex
* the index of the OPARAM to return. Can use a negative index to
* count from the end (-1 equals the last OPARAM rendered with that
* name).
*
* @return the specified OPARAM, or null if an OPARAM with that name and
* index does not exist.
*/
public RenderedOutputParameter getRenderedOutputParameter(String pName, int pIndex) {
List<RenderedOutputParameter> listParams = getRenderedOutputParametersByName(pName);
RenderedOutputParameter paramResult = null;
if (listParams != null) {
int index = pIndex;
if (index < 0) {
// offset from the end
index = listParams.size() + index;
}
if ((index >= 0) && (index < listParams.size())) {
paramResult = listParams.get(pIndex);
}
}
return paramResult;
}
/**
* A convenience method to get the specified parameter from the parameter
* stack from of a recorded OPARAM invocation.
*
* @param pParameterName
* the name of the parameter whose value should be returned
* (for
* example, "element")
* @param pOutputParameterName
* the name of the output parameter whose parameter stack frame
* should be used (for example, "output" or "error")
* @param pIndex
* the index of the Nth invocation of the output parameter.
*
* @return the value of the parameter from the named OPARAM invocation, or
* null if either the OPARAM invocation or the parameter to be
* fetched were not found.
*/
public Object getFrameParameterOfRenderedParameter(String pParameterName,
String pOutputParameterName,
int pIndex) {
RenderedOutputParameter oparam = getRenderedOutputParameter(
pOutputParameterName, pIndex
);
if (oparam != null) {
return oparam.getFrameParameter(pParameterName);
}
return null;
}
} // end inner-class DropletResult
// -------------------------------------------------------
/**
* Our implementation of ServiceParameterCallback.
*/
public class ServiceParameterCallbackImpl
implements ServiceParameterCallback {
/**
* The droplet result to which we will add RenderedOutputParameter.
*/
final DropletResult mDropletResult;
/**
* Create a new instance that will invoke addRenderedParameter to
* DropletResult when didServiceParameter() is invoked.
*
* @param pDropletResult
* to droplet result to record OPARAM rending one.
*/
public ServiceParameterCallbackImpl(DropletResult pDropletResult) {
mDropletResult = pDropletResult;
}
/**
* In this case, create a new RenderedOutputParameter and add it to our
* list.
*/
public boolean didServiceParameter(String pParameterName,
ServletRequest pRequest,
ServletResponse pResponse,
TagConverter pCvt,
Properties pCvtArgs,
boolean pResult,
boolean pIsLocal) {
mDropletResult.addRenderedParameter(
createRenderedOutputParameter(
pParameterName,
(DynamoHttpServletRequest) pRequest,
(DynamoHttpServletResponse) pResponse
)
);
boolean bResult = pResult;
if (null != mOparamExistsOverrideMap.get(pParameterName)) {
// override map always wins
bResult = mOparamExistsOverrideMap.get(pParameterName);
}
else {
if (!bResult && getOparamsExistByDefault()) {
bResult = true;
}
}
return bResult;
}
} // end inner-class ServiceParameterCallbackImpl
}