/**
* Copyright (C) 2011 Orbeon, Inc.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.xforms.submission;
import org.orbeon.oxf.externalcontext.ExternalContext;
import org.orbeon.oxf.util.ConnectionResult;
import org.orbeon.oxf.util.IndentedLogger;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.xforms.XFormsConstants;
import org.orbeon.oxf.xforms.XFormsContainingDocument;
import org.orbeon.oxf.xforms.XFormsUtils;
import java.net.URI;
import java.util.concurrent.Callable;
/**
* Submission which doesn't issue HTTP requests but goes through a Servlet or Portlet API's RequestDispatcher.
*/
public class RequestDispatcherSubmission extends BaseSubmission {
private static final String SKIPPING_SUBMISSION_DEBUG_MESSAGE = "skipping request dispatcher servlet submission";
public RequestDispatcherSubmission(XFormsModelSubmission submission) {
super(submission);
}
public String getType() {
return "request dispatcher";
}
/**
* Check whether submission is allowed.
*/
public boolean isMatch(SubmissionParameters p,
SecondPassParameters p2, SerializationParameters sp) {
final ExternalContext.Request request = NetUtils.getExternalContext().getRequest();
final IndentedLogger indentedLogger = getDetailsLogger(p, p2);
// Log a lot of stuff for development, as it is not always obvious why we pick this type of submission.
final boolean isDebugEnabled = indentedLogger.isDebugEnabled();
if (isDebugEnabled) {
indentedLogger.logDebug("", "checking whether " + getType() + " submission is allowed",
"resource", p2.actionOrResource(),
"noscript", Boolean.toString(p.isNoscript()),
"is asynchronous", Boolean.toString(p2.isAsynchronous()),
"container type", request.getContainerType(),
"norewrite", Boolean.toString(submission().isURLNorewrite()),
"url type", submission().getUrlType(),
"local-submission-forward", Boolean.toString(containingDocument().isLocalSubmissionForward()),
"local-submission-include", Boolean.toString(containingDocument().isLocalSubmissionInclude())
);
}
// Only for servlet for now
if (! request.getContainerType().equals("servlet")) {
if (isDebugEnabled)
indentedLogger.logDebug("", SKIPPING_SUBMISSION_DEBUG_MESSAGE,
"reason", "container type is not servlet");
return false;
}
// Separate deployment not supported for portlet local submission as callee is a servlet, not a portlet!
if (! containingDocument().getDeploymentType().equals(XFormsConstants.DeploymentType.separate)) {
if (isDebugEnabled)
indentedLogger.logDebug("", SKIPPING_SUBMISSION_DEBUG_MESSAGE,
"reason", "deployment type is not separate");
return false;
}
// Absolute URL implies a regular submission
if (NetUtils.urlHasProtocol(p2.actionOrResource())) {
if (isDebugEnabled)
indentedLogger.logDebug("", SKIPPING_SUBMISSION_DEBUG_MESSAGE,
"reason", "resource URL has protocol", "resource", p2.actionOrResource());
return false;
}
// TODO: why is this condition here?
if (p.isNoscript()) {
if (isDebugEnabled)
indentedLogger.logDebug("", SKIPPING_SUBMISSION_DEBUG_MESSAGE,
"reason", "noscript mode enabled");
return false;
}
// For now, we don't handle async (could be handled in the future)
if (p2.isAsynchronous()) {
if (isDebugEnabled)
indentedLogger.logDebug("", SKIPPING_SUBMISSION_DEBUG_MESSAGE,
"reason", "asynchronous mode is not supported yet");
return false;
}
if (ReplaceType.isReplaceAll(p.replaceType())) {
// replace="all"
if (! containingDocument().isLocalSubmissionForward()) {
if (isDebugEnabled)
indentedLogger.logDebug("", SKIPPING_SUBMISSION_DEBUG_MESSAGE,
"reason", "forward submissions are disallowed in properties");
return false;
}
} else {
// replace="instance|text|none"
if (! containingDocument().isLocalSubmissionInclude()) {
if (isDebugEnabled)
indentedLogger.logDebug("", SKIPPING_SUBMISSION_DEBUG_MESSAGE,
"reason", "include submissions are disallowed in properties");
return false;
}
}
if (isDebugEnabled)
indentedLogger.logDebug("", "enabling " + getType() + " submission");
return true;
}
public SubmissionResult connect(final SubmissionParameters p,
final SecondPassParameters p2, final SerializationParameters sp) throws Exception {
final IndentedLogger timingLogger = getTimingLogger(p, p2);
final IndentedLogger detailsLogger = getDetailsLogger(p, p2);
// NOTE: Using include() for servlets doesn't allow detecting errors caused by the
// included resource. [As of 2009-02-13, not sure if this is the case.]
// f:url-norewrite="true" with an absolute path allows accessing other servlet contexts.
// URI with xml:base resolution
final URI resolvedURI = XFormsUtils.resolveXMLBase(containingDocument(), submission().getSubmissionElement(), p2.actionOrResource());
// NOTE: We don't want any changes to happen to the document upon xxforms-submit when producing
// a new document so we don't dispatch xforms-submit-done and pass a null XFormsModelSubmission
// in that case
// Headers
final scala.collection.immutable.Map<String, scala.collection.immutable.List<String>> customHeaderNameValues =
SubmissionUtils.evaluateHeaders(submission(), ReplaceType.isReplaceAll(p.replaceType()));
final String submissionEffectiveId = submission().getEffectiveId();
// Pack external call into a Runnable so it can be run:
// - now and synchronously
// - now and asynchronously
// - later as a "foreground" asynchronous submission
final Callable<SubmissionResult> callable = new Callable<SubmissionResult>() {
public SubmissionResult call() throws Exception {
// TODO: This refers to PropertyContext, XFormsContainingDocument, and Submission. Ok because async disabled for now.
// Open the connection
final boolean[] status = { false , false};
ConnectionResult connectionResult = null;
try {
connectionResult = openRequestDispatcherConnection(NetUtils.getExternalContext(),
containingDocument(), detailsLogger, resolvedURI.toString(), p,
submission().isURLNorewrite(), sp.actualRequestMediatype(), p2.encoding(), sp.messageBody(),
sp.queryString(), customHeaderNameValues);
// Update status
status[0] = true;
// TODO: can we put this in the Replacer?
if (connectionResult.dontHandleResponse())
containingDocument().setGotSubmissionReplaceAll();
// Obtain replacer, deserialize and update status
final Replacer replacer = submission().getReplacer(connectionResult, p);
replacer.deserialize(connectionResult, p, p2);
status[1] = true;
// Return result
return new SubmissionResult(submissionEffectiveId, replacer, connectionResult);
} catch (Throwable throwable) {
// Exceptions are handled further down
return new SubmissionResult(submissionEffectiveId, throwable, connectionResult);
} finally {
if (p2.isAsynchronous() && timingLogger.isDebugEnabled())
timingLogger.endHandleOperation("id", submissionEffectiveId, "asynchronous", Boolean.toString(p2.isAsynchronous()),
"connected", Boolean.toString(status[0]), "deserialized", Boolean.toString(status[1]));
}
}
};
// Submit the callable
// This returns null if the execution is deferred
return submitCallable(p, p2, callable);
}
/**
* Perform a local connection using the Servlet API.
*/
public ConnectionResult openRequestDispatcherConnection(
ExternalContext externalContext,
XFormsContainingDocument containingDocument,
IndentedLogger indentedLogger,
final String resource,
final SubmissionParameters p,
boolean isNorewrite,
String actualRequestMediatype,
String encoding,
byte[] messageBody,
String queryString,
scala.collection.immutable.Map<String, scala.collection.immutable.List<String>> customHeaderNameValues
) {
// NOTE: This code does custom rewriting of the path on the action, taking into account whether
// the page was produced through a filter in separate deployment or not.
final boolean isContextRelative;
final String effectiveResource;
if (! isNorewrite) {
// Must rewrite
if (! containingDocument.getDeploymentType().equals(XFormsConstants.DeploymentType.separate)) {
// We are not in separate deployment, so keep path relative to the current servlet context
isContextRelative = true;
effectiveResource = resource;
} else {
// We are in separate deployment, so prepend request context path and mark path as not relative to the current context`
final String contextPath = containingDocument.getRequestContextPath();
isContextRelative = false;
effectiveResource = contextPath + resource;
}
} else {
// Must not rewrite anyway, so mark path as not relative to the current context
isContextRelative = false;
effectiveResource = resource;
}
final ExternalContext.RequestDispatcher requestDispatcher = externalContext.getRequestDispatcher(effectiveResource, isContextRelative);
final boolean isDefaultContext = requestDispatcher.isDefaultContext();
// 2017-05-11: Checked that only `WebAppExternalContext.getResponse` can return `null`. So we probably don't
// need to support `null` response in `openLocalConnection`.
final ExternalContext.Response response =
containingDocument.getResponse() != null ? containingDocument.getResponse() : externalContext.getResponse();
return openLocalConnection(
externalContext.getRequest(),
response,
indentedLogger,
effectiveResource,
p,
actualRequestMediatype,
encoding,
messageBody,
queryString,
customHeaderNameValues,
new SubmissionProcess() {
public void process(ExternalContext.Request request, ExternalContext.Response response) {
if (ReplaceType.isReplaceAll(p.replaceType()))
requestDispatcher.forward(request, response);
else
requestDispatcher.include(request, response);
}
},
isContextRelative,
isDefaultContext
);
}
}