0001: /*
0002: * $Id: FrontendCallingConvention.java,v 1.64 2007/09/18 08:45:08 agoubard Exp $
0003: *
0004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
0005: * See the COPYRIGHT file for redistribution and use restrictions.
0006: */
0007: package org.xins.server.frontend;
0008:
0009: import java.io.InputStream;
0010: import java.io.IOException;
0011: import java.io.PrintWriter;
0012: import java.io.StringReader;
0013: import java.io.StringWriter;
0014: import java.io.Writer;
0015: import java.net.URL;
0016: import java.util.ArrayList;
0017: import java.util.Enumeration;
0018: import java.util.Iterator;
0019: import java.util.HashMap;
0020: import java.util.List;
0021: import java.util.Map;
0022: import java.util.Properties;
0023: import java.util.Set;
0024: import java.util.TreeMap;
0025:
0026: import javax.xml.transform.Result;
0027: import javax.xml.transform.Source;
0028: import javax.xml.transform.SourceLocator;
0029: import javax.xml.transform.Templates;
0030: import javax.xml.transform.Transformer;
0031: import javax.xml.transform.TransformerConfigurationException;
0032: import javax.xml.transform.TransformerException;
0033: import javax.xml.transform.TransformerFactory;
0034: import javax.xml.transform.stream.StreamResult;
0035: import javax.xml.transform.stream.StreamSource;
0036:
0037: import javax.servlet.http.Cookie;
0038: import javax.servlet.http.HttpServletRequest;
0039: import javax.servlet.http.HttpServletResponse;
0040:
0041: import org.xins.common.MandatoryArgumentChecker;
0042: import org.xins.common.collections.BasicPropertyReader;
0043: import org.xins.common.collections.ChainedMap;
0044: import org.xins.common.collections.InvalidPropertyValueException;
0045: import org.xins.common.collections.MissingRequiredPropertyException;
0046: import org.xins.common.collections.PropertyReader;
0047: import org.xins.common.io.IOReader;
0048: import org.xins.common.manageable.BootstrapException;
0049: import org.xins.common.manageable.InitializationException;
0050: import org.xins.common.spec.FunctionSpec;
0051: import org.xins.common.text.ParseException;
0052: import org.xins.common.text.TextUtils;
0053: import org.xins.common.types.EnumItem;
0054: import org.xins.common.types.standard.Date;
0055: import org.xins.common.types.standard.Timestamp;
0056: import org.xins.common.xml.Element;
0057: import org.xins.common.xml.ElementBuilder;
0058: import org.xins.common.xml.ElementParser;
0059: import org.xins.common.xml.ElementSerializer;
0060:
0061: import org.xins.server.API;
0062: import org.xins.server.CustomCallingConvention;
0063: import org.xins.server.Function;
0064: import org.xins.server.FunctionNotSpecifiedException;
0065: import org.xins.server.FunctionRequest;
0066: import org.xins.server.FunctionResult;
0067: import org.xins.server.InvalidRequestException;
0068: import org.xins.server.Log;
0069: import org.znerd.xmlenc.XMLOutputter;
0070:
0071: /**
0072: * XINS Front-end Framework calling convention.
0073: *
0074: * @version $Revision: 1.64 $ $Date: 2007/09/18 08:45:08 $
0075: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
0076: *
0077: * @since XINS 1.5.0.
0078: */
0079: public class FrontendCallingConvention extends CustomCallingConvention {
0080:
0081: /**
0082: * The response encoding format.
0083: */
0084: private static final String RESPONSE_ENCODING = "ISO-8859-1";
0085:
0086: /**
0087: * The content type of the HTTP response.
0088: */
0089: private static final String XML_CONTENT_TYPE = "text/xml;charset="
0090: + RESPONSE_ENCODING;
0091:
0092: /**
0093: * The content type of the HTTP response.
0094: */
0095: private static final String HTML_CONTENT_TYPE = "text/html;charset="
0096: + RESPONSE_ENCODING;
0097:
0098: /**
0099: * The name of the runtime property that defines if the templates should be
0100: * cached. Should be either <code>"true"</code> or <code>"false"</code>.
0101: * By default the cache is enabled.
0102: */
0103: private static final String TEMPLATES_CACHE_PROPERTY = "templates.cache";
0104:
0105: /**
0106: * Argument used when calling function with no parameters using the reflection API.
0107: */
0108: private static final Object[] NO_ARGS = {};
0109:
0110: /**
0111: * Argument used when finding a function with no parameters using the reflection API.
0112: */
0113: private static final Class[] NO_ARGS_CLASS = {};
0114:
0115: /**
0116: * The API. Never <code>null</code>.
0117: */
0118: private final API _api;
0119:
0120: /**
0121: * Session manager.
0122: */
0123: private SessionManager _session;
0124:
0125: /**
0126: * Location of the XSLT transformation Style Sheet.
0127: */
0128: private String _baseXSLTDir;
0129:
0130: /**
0131: * The XSLT transformer.
0132: */
0133: private TransformerFactory _factory;
0134:
0135: /**
0136: * The default page, cannot be <code>null</code>.
0137: */
0138: private String _defaultCommand;
0139:
0140: /**
0141: * The login page or <code>null</code> if the framework does no have any login page.
0142: */
0143: private String _loginPage;
0144:
0145: /**
0146: * The error page or <code>null</code> if the framework does no have any special error page.
0147: */
0148: private String _errorPage;
0149:
0150: /**
0151: * Redirection map. The key is the command and the value is the redirection
0152: * command.
0153: */
0154: private Map _redirectionMap = new ChainedMap();
0155:
0156: /**
0157: * Conditional redirection map. The key is the command and the value is the
0158: * {@link Templates} that will return the name of the redirection command.
0159: */
0160: private Map _conditionalRedirectionMap = new HashMap();
0161:
0162: /**
0163: * Flag that indicates whether the templates should be cached. This field
0164: * is set during initialization.
0165: */
0166: private boolean _cacheTemplates;
0167:
0168: /**
0169: * Cache for the XSLT templates. Never <code>null</code>.
0170: */
0171: private Map _templateCache = new HashMap();
0172:
0173: /**
0174: * The template used for the Control command.
0175: */
0176: private Templates _templateControl;
0177:
0178: /**
0179: * The template used for the error page.
0180: */
0181: private Templates _templateError;
0182:
0183: /**
0184: * The list of the real function names for this API.
0185: */
0186: private List _functionList = new ArrayList();
0187:
0188: /**
0189: * Creates a new <code>FrontendCallingConvention</code> instance.
0190: *
0191: * @param api
0192: * the API, needed for the SOAP messages, cannot be <code>null</code>.
0193: *
0194: * @throws IllegalArgumentException
0195: * if <code>api == null</code>.
0196: */
0197: public FrontendCallingConvention(API api)
0198: throws IllegalArgumentException {
0199:
0200: // Check arguments
0201: MandatoryArgumentChecker.check("api", api);
0202:
0203: // Store the API
0204: _api = api;
0205:
0206: // Get the session manager manageable from the API
0207: try {
0208: _session = (SessionManager) api.getClass().getMethod(
0209: "getSessionManager", NO_ARGS_CLASS).invoke(api,
0210: NO_ARGS);
0211: } catch (Exception ex) {
0212: Log.log_3700(ex);
0213: }
0214: }
0215:
0216: protected void bootstrapImpl(PropertyReader bootstrapProperties)
0217: throws MissingRequiredPropertyException,
0218: InvalidPropertyValueException, BootstrapException {
0219: _loginPage = bootstrapProperties.get("xinsff.login.page");
0220: _errorPage = bootstrapProperties.get("xinsff.error.page");
0221: _defaultCommand = bootstrapProperties
0222: .get("xinsff.default.command");
0223: if (_defaultCommand == null) {
0224: _defaultCommand = "DefaultCommand";
0225: }
0226:
0227: // Creates the transformer factory
0228: _factory = TransformerFactory.newInstance();
0229:
0230: initRedirections(bootstrapProperties);
0231: }
0232:
0233: protected void initImpl(PropertyReader runtimeProperties)
0234: throws MissingRequiredPropertyException,
0235: InvalidPropertyValueException, InitializationException {
0236:
0237: // Get the base directory of the Style Sheet
0238: String templatesProperty = "templates." + _api.getName()
0239: + ".xinsff.source";
0240: _baseXSLTDir = runtimeProperties.get(templatesProperty);
0241: if (_baseXSLTDir == null) {
0242: throw new MissingRequiredPropertyException(
0243: templatesProperty);
0244: }
0245: Properties systemProps = System.getProperties();
0246: _baseXSLTDir = TextUtils.replace(_baseXSLTDir, systemProps,
0247: "${", "}");
0248: _baseXSLTDir = _baseXSLTDir.replace('\\', '/');
0249:
0250: // Determine if the template cache should be enabled
0251: String cacheEnabled = runtimeProperties
0252: .get(TEMPLATES_CACHE_PROPERTY);
0253: initCacheEnabled(cacheEnabled);
0254:
0255: // Gets the functions of the API
0256: Iterator itFunctions = _api.getFunctionList().iterator();
0257: while (itFunctions.hasNext()) {
0258: Function nextFunction = (Function) itFunctions.next();
0259: _functionList.add(nextFunction.getName());
0260: }
0261: }
0262:
0263: /**
0264: * Determines if the template cache should be enabled. If no value is
0265: * passed, then by default the cache is enabled. An invalid value, however,
0266: * will trigger an {@link InvalidPropertyValueException}.
0267: *
0268: * @param cacheEnabled
0269: * the value of the runtime property that specifies whether the cache
0270: * should be enabled, can be <code>null</code>.
0271: *
0272: * @throws InvalidPropertyValueException
0273: * if the value is incorrect.
0274: */
0275: private void initCacheEnabled(String cacheEnabled)
0276: throws InvalidPropertyValueException {
0277:
0278: // By default, the template cache is enabled
0279: if (TextUtils.isEmpty(cacheEnabled)) {
0280: _cacheTemplates = true;
0281:
0282: // Trim before comparing with 'true' and 'false'
0283: } else {
0284: cacheEnabled = cacheEnabled.trim();
0285: if ("true".equals(cacheEnabled)) {
0286: _cacheTemplates = true;
0287: } else if ("false".equals(cacheEnabled)) {
0288: _cacheTemplates = false;
0289: } else {
0290: throw new InvalidPropertyValueException(
0291: TEMPLATES_CACHE_PROPERTY, cacheEnabled,
0292: "Expected either \"true\" or \"false\".");
0293: }
0294: }
0295: }
0296:
0297: protected boolean matches(HttpServletRequest httpRequest)
0298: throws Exception {
0299:
0300: return (httpRequest.getMethod().equalsIgnoreCase("GET") && httpRequest
0301: .getParameterMap().size() == 0)
0302: || !TextUtils.isEmpty(httpRequest
0303: .getParameter("command"));
0304: }
0305:
0306: /**
0307: * Converts an HTTP request to a XINS request (implementation method). This
0308: * method should only be called from class {@link CustomCallingConvention}.
0309: * Only then it is guaranteed that the <code>httpRequest</code> argument is
0310: * not <code>null</code>.
0311: *
0312: * @param httpRequest
0313: * the HTTP request, will not be <code>null</code>.
0314: *
0315: * @return
0316: * the XINS request object, never <code>null</code>.
0317: *
0318: * @throws InvalidRequestException
0319: * if the request is considerd to be invalid.
0320: *
0321: * @throws FunctionNotSpecifiedException
0322: * if the request does not indicate the name of the function to execute.
0323: */
0324: protected FunctionRequest convertRequestImpl(
0325: HttpServletRequest httpRequest)
0326: throws InvalidRequestException,
0327: FunctionNotSpecifiedException {
0328:
0329: // Determine function name
0330: String functionName = httpRequest.getParameter("command");
0331: if (functionName == null || functionName.equals("")) {
0332: functionName = _defaultCommand;
0333: }
0334:
0335: _session.request(httpRequest);
0336:
0337: // Control command has a special behaviour
0338: if ("Control".equals(functionName)) {
0339: String action = httpRequest.getParameter("action");
0340: if ("ReadConfigFile".equals(action)) {
0341: functionName = "_ReloadProperties";
0342: }
0343: return new FunctionRequest(functionName, null, null, true);
0344: }
0345:
0346: // Append the action to the function name
0347: String actionName = httpRequest.getParameter("action");
0348: if (actionName != null && !actionName.equals("")
0349: && !actionName.toLowerCase().equals("show")) {
0350: functionName += TextUtils.firstCharUpper(actionName);
0351: }
0352:
0353: // Redirect to the login page if not logged in or the function is not implemented
0354: if (_session.shouldLogIn()
0355: || (_redirectionMap.get(functionName) != null && !_functionList
0356: .contains(functionName))) {
0357: return new FunctionRequest(functionName, null, null, true);
0358: }
0359:
0360: // Determine function parameters
0361: BasicPropertyReader functionParams = new BasicPropertyReader();
0362: Enumeration params = httpRequest.getParameterNames();
0363: while (params.hasMoreElements()) {
0364: String name = (String) params.nextElement();
0365:
0366: // TODO remove the next line when no longer needed.
0367: String realName = getRealParameter(name, functionName);
0368: String value = httpRequest.getParameter(name);
0369: functionParams.set(realName, value);
0370: }
0371:
0372: // Get data section
0373: String dataSectionValue = httpRequest.getParameter("_data");
0374: Element dataElement;
0375: if (dataSectionValue != null && dataSectionValue.length() > 0) {
0376: ElementParser parser = new ElementParser();
0377:
0378: // Parse the data section
0379: try {
0380: dataElement = parser.parse(new StringReader(
0381: dataSectionValue));
0382:
0383: // I/O error, should never happen on a StringReader
0384: } catch (IOException ex) {
0385: throw new InvalidRequestException(
0386: "Cannot parse the data section.", ex);
0387: // Parsing error
0388: } catch (ParseException ex) {
0389: throw new InvalidRequestException(
0390: "Cannot parse the data section.", ex);
0391: }
0392: } else {
0393: dataElement = null;
0394: }
0395:
0396: // Construct and return the request object
0397: return new FunctionRequest(functionName, functionParams,
0398: dataElement);
0399: }
0400:
0401: /**
0402: * Converts a XINS result to an HTTP response (implementation method).
0403: *
0404: * @param xinsResult
0405: * the XINS result object that should be converted to an HTTP response,
0406: * will not be <code>null</code>.
0407: *
0408: * @param httpResponse
0409: * the HTTP response object to configure, will not be <code>null</code>.
0410: *
0411: * @param httpRequest
0412: * the HTTP request sent by the client, will not be <code>null</code>.
0413: *
0414: * @throws IOException
0415: * if calling any of the methods in <code>httpResponse</code> causes an
0416: * I/O error.
0417: */
0418: protected void convertResultImpl(FunctionResult xinsResult,
0419: HttpServletResponse httpResponse,
0420: HttpServletRequest httpRequest) throws IOException {
0421:
0422: addSessionCookie(httpRequest, httpResponse);
0423:
0424: String mode = httpRequest.getParameter("mode");
0425: String command = httpRequest.getParameter("command");
0426: if (command == null || command.equals("")) {
0427: command = _defaultCommand;
0428: }
0429: String action = httpRequest.getParameter("action");
0430: if (action == null || action.equals("show")) {
0431: action = "";
0432: }
0433: String functionName = command + action;
0434:
0435: _session.result(xinsResult.getErrorCode() == null);
0436:
0437: // Display the XSLT
0438: if ("template".equalsIgnoreCase(mode)) {
0439: String xsltSource = getCommandXSLT(command);
0440: httpResponse.setContentType(XML_CONTENT_TYPE);
0441: httpResponse.setStatus(HttpServletResponse.SC_OK);
0442: Writer output = httpResponse.getWriter();
0443: output.write(xsltSource);
0444: output.close();
0445: return;
0446: }
0447:
0448: // Control command
0449: if ("Control".equals(command)) {
0450: xinsResult = control(action);
0451: }
0452:
0453: Element commandResult = null;
0454: String commandResultXML = null;
0455: if (_conditionalRedirectionMap.get(functionName) != null) {
0456: commandResult = createXMLResult(httpRequest, xinsResult);
0457: commandResultXML = serializeResult(commandResult);
0458: }
0459:
0460: // Redirection
0461: String redirection = getRedirection(xinsResult, command,
0462: functionName, commandResultXML);
0463: if (redirection != null) {
0464: if ("source".equals(mode)) {
0465: redirection += "&mode=source";
0466: }
0467: httpResponse.sendRedirect(redirection);
0468: return;
0469: }
0470:
0471: if (commandResult == null) {
0472: commandResult = createXMLResult(httpRequest, xinsResult);
0473: commandResultXML = serializeResult(commandResult);
0474: }
0475:
0476: if ("source".equalsIgnoreCase(mode)) {
0477: PrintWriter out = httpResponse.getWriter();
0478: httpResponse.setContentType(XML_CONTENT_TYPE);
0479: httpResponse.setStatus(HttpServletResponse.SC_OK);
0480: out.print(commandResultXML);
0481: out.close();
0482: } else if (command != null) {
0483: /*if (command.endsWith("Show") || command.endsWith("Okay")) {
0484: command = command.substring(0, command.length() - 4);
0485: }*/
0486: String xsltLocation = _baseXSLTDir + command + ".xslt";
0487: try {
0488: Templates template = null;
0489: if ("Control".equals(command)
0490: && _templateControl == null) {
0491: try {
0492: StringReader controlXSLT = new StringReader(
0493: ControlResult.getControlTemplate());
0494: _templateControl = _factory
0495: .newTemplates(new StreamSource(
0496: controlXSLT));
0497: template = _templateControl;
0498: } catch (TransformerConfigurationException tcex) {
0499: Log.log_3701(tcex, "control");
0500: }
0501: } else if ("Control".equals(command)) {
0502: template = _templateControl;
0503: } else {
0504: template = getTemplate(xsltLocation);
0505: }
0506: Log.log_3704(command);
0507: String resultHTML = translate(commandResultXML,
0508: template);
0509: String contentType = getContentType(template
0510: .getOutputProperties());
0511: PrintWriter out = httpResponse.getWriter();
0512: httpResponse.setContentType(contentType);
0513: httpResponse.setStatus(HttpServletResponse.SC_OK);
0514: out.print(resultHTML);
0515: out.close();
0516: } catch (TransformerConfigurationException tcex) {
0517: showError(tcex, httpResponse, httpRequest);
0518: } catch (TransformerException tex) {
0519: showError(tex, httpResponse, httpRequest);
0520: } catch (Exception ex) {
0521:
0522: // Logging of the specific exception is done by the method called
0523: throw new IOException(ex.getMessage());
0524: }
0525: }
0526: }
0527:
0528: /**
0529: * Adds the session ID to the cookies.
0530: *
0531: * @param httpRequest
0532: * the HTTP request, cannot be <code>null</code>.
0533: *
0534: * @param httpResponse
0535: * the HTTP response, cannot be <code>null</code>.
0536: */
0537: private void addSessionCookie(HttpServletRequest httpRequest,
0538: HttpServletResponse httpResponse) {
0539: Cookie cookie = new Cookie("SessionID", _session.getSessionId());
0540:
0541: // Determine domain for the session cookie
0542: String host = httpRequest.getHeader("host");
0543: if (TextUtils.isEmpty(host)) {
0544: host = httpRequest.getRemoteHost();
0545: }
0546:
0547: String domain = host;
0548:
0549: //strip subdomain from the host
0550: if (host.indexOf(".") != -1) {
0551: domain = host.substring(host.indexOf("."));
0552: }
0553:
0554: //strip port if any
0555: if (domain.indexOf(":") != -1) {
0556: domain = domain.substring(0, domain.indexOf(":"));
0557: }
0558:
0559: cookie.setDomain(domain);
0560: cookie.setPath("/");
0561:
0562: httpResponse.addCookie(cookie);
0563: }
0564:
0565: /**
0566: * Creates the GPF XML from the result returned by the function
0567: * and in the session.
0568: *
0569: * @param httpRequest
0570: * the HTTP request, cannot be <code>null</code>.
0571: *
0572: * @param xinsResult
0573: * the result returned by the function, cannot be <code>null</code>.
0574: *
0575: * @return
0576: * The XML Element containing the GPF XML result.
0577: */
0578: private Element createXMLResult(HttpServletRequest httpRequest,
0579: FunctionResult xinsResult) {
0580:
0581: // Create the source element
0582: ElementBuilder builder = new ElementBuilder("commandresult");
0583: builder.setAttribute("command", httpRequest
0584: .getParameter("command"));
0585: //builder.setAttribute("description", "Description of " + httpRequest.getParameter("command") + '.');
0586: ElementBuilder dataSection = new ElementBuilder("data");
0587:
0588: // Put all the sessions in the XML
0589: Map sessionProperties = _session.getProperties();
0590: if (sessionProperties != null) {
0591: Iterator itSessionProperties = sessionProperties.entrySet()
0592: .iterator();
0593: while (itSessionProperties.hasNext()) {
0594: Map.Entry nextEntry = (Map.Entry) itSessionProperties
0595: .next();
0596: String nextProperty = (String) nextEntry.getKey();
0597: Object propValue = nextEntry.getValue();
0598: if (propValue instanceof Element
0599: && ((Element) propValue).getLocalName().equals(
0600: "data")) {
0601: propValue = ((Element) propValue)
0602: .getChildElements();
0603: }
0604: if (nextProperty.startsWith("_") || propValue == null) {
0605: // continue
0606: } else if (propValue instanceof String
0607: || propValue instanceof Number
0608: || propValue instanceof Boolean
0609: || propValue instanceof EnumItem
0610: || propValue instanceof Date.Value
0611: || propValue instanceof Timestamp.Value) {
0612: ElementBuilder builderParam = new ElementBuilder(
0613: "parameter");
0614: builderParam.setAttribute("name", "session."
0615: + nextProperty);
0616: builderParam.setText(propValue.toString());
0617: builder.addChild(builderParam.createElement());
0618: } else if ("org.jdom.Element".equals(propValue
0619: .getClass().getName())) {
0620: //org.jdom.Element propElem = (org.jdom.Element) propValue;
0621: // TODO dataSection.addChild(Utils.convertFromJDOM(propValue));
0622: } else if (propValue instanceof Element) {
0623: dataSection.addChild((Element) propValue);
0624: } else if (propValue instanceof List) {
0625: Iterator itPropValue = ((List) propValue)
0626: .iterator();
0627: while (itPropValue.hasNext()) {
0628: Object nextPropertyInList = itPropValue.next();
0629: if (nextPropertyInList == null) {
0630: // continue
0631: } else if ("org.jdom.Element"
0632: .equals(nextPropertyInList.getClass()
0633: .getName())) {
0634: //org.jdom.Element propElem = (org.jdom.Element) nextPropertyInList;
0635: // TODO dataSection.addChild(Utils.convertFromJDOM(nextPropertyInList));
0636: } else if (nextPropertyInList instanceof Element) {
0637: dataSection
0638: .addChild((Element) nextPropertyInList);
0639: }
0640: }
0641: }
0642: }
0643: }
0644:
0645: // Store all the input parameters also in the XML
0646: Enumeration inputParameterNames = httpRequest
0647: .getParameterNames();
0648: while (inputParameterNames.hasMoreElements()) {
0649: String nextParameter = (String) inputParameterNames
0650: .nextElement();
0651: ElementBuilder builderParam = new ElementBuilder(
0652: "parameter");
0653: builderParam.setAttribute("name", "input." + nextParameter);
0654: builderParam.setText(httpRequest
0655: .getParameter(nextParameter));
0656: builder.addChild(builderParam.createElement());
0657: }
0658:
0659: // Store all the returned parameters also in the XML
0660: PropertyReader parameters = xinsResult.getParameters();
0661: if (parameters != null) {
0662: Iterator parameterNames = parameters.getNames();
0663: while (parameterNames.hasNext()) {
0664: String nextParameter = (String) parameterNames.next();
0665: if (!"redirect".equals(nextParameter)) {
0666: ElementBuilder builderParam = new ElementBuilder(
0667: "parameter");
0668: builderParam.setAttribute("name", nextParameter);
0669: builderParam.setText(parameters.get(nextParameter));
0670: builder.addChild(builderParam.createElement());
0671: }
0672: }
0673: }
0674:
0675: // Store the error code
0676: if (xinsResult.getErrorCode() != null) {
0677: if (xinsResult.getErrorCode().equals("_InvalidRequest")
0678: || xinsResult.getErrorCode().equals(
0679: "InvalidRequest")) {
0680: addParameter(builder, "error.type", "FieldError");
0681: ElementBuilder errorSection = new ElementBuilder(
0682: "errorlist");
0683: Iterator incorrectParams = xinsResult.getDataElement()
0684: .getChildElements().iterator();
0685: while (incorrectParams.hasNext()) {
0686: Element incorrectParamElement = (Element) incorrectParams
0687: .next();
0688: String elementName = incorrectParamElement
0689: .getLocalName();
0690:
0691: // param-combo not supported for xins ff
0692: if (elementName.equals("param-combo")) {
0693: Iterator incorrectParamCombo = incorrectParamElement
0694: .getChildElements("param").iterator();
0695: while (incorrectParamCombo.hasNext()) {
0696: Element incorrectParamComboElement = (Element) incorrectParamCombo
0697: .next();
0698: String paramName = incorrectParamComboElement
0699: .getAttribute("name");
0700: Element fieldError = createFieldError(
0701: elementName, paramName);
0702: errorSection.addChild(fieldError);
0703: }
0704: } else {
0705: String paramName = incorrectParamElement
0706: .getAttribute("param");
0707: Element fieldError = createFieldError(
0708: elementName, paramName);
0709: errorSection.addChild(fieldError);
0710: }
0711: }
0712: dataSection.addChild(errorSection.createElement());
0713: builder.addChild(dataSection.createElement());
0714: return builder.createElement();
0715: } else {
0716: addParameter(builder, "error.type", "FunctionError");
0717: addParameter(builder, "error.code", xinsResult
0718: .getErrorCode());
0719: }
0720: }
0721:
0722: // Store the data section as it is
0723: Element resultElement = xinsResult.getDataElement();
0724: if (resultElement != null) {
0725: Iterator itChildren = resultElement.getChildElements()
0726: .iterator();
0727: while (itChildren.hasNext()) {
0728: dataSection.addChild((Element) itChildren.next());
0729: }
0730: }
0731: builder.addChild(dataSection.createElement());
0732: return builder.createElement();
0733: }
0734:
0735: /**
0736: * Creates a field error based on the error based on the error returned
0737: * by the function.
0738: *
0739: * @param elementName
0740: * the name of the error element, cannot be <code>null</code>.
0741: *
0742: * @param paramName
0743: * the name of the incorrect parameter, cannot be <code>null</code>.
0744: *
0745: * @return
0746: * the field error element, never <code>null</code>.
0747: */
0748: private Element createFieldError(String elementName,
0749: String paramName) {
0750: paramName = getOriginalParameter(paramName);
0751: ElementBuilder fieldError = new ElementBuilder("fielderror");
0752: fieldError.setAttribute("field", paramName);
0753: if (elementName.equals("missing-param")) {
0754: fieldError.setAttribute("type", "mand");
0755: } else if (elementName.equals("invalid-value-for-type")) {
0756: fieldError.setAttribute("type", "format");
0757: } else {
0758: fieldError.setAttribute("type", elementName);
0759: }
0760: return fieldError.createElement();
0761: }
0762:
0763: /**
0764: * Adds a parameter element to the XML result.
0765: *
0766: * @param builder
0767: * the ElementBuilder where the parameter should be added.
0768: *
0769: * @param name
0770: * the name of the parameter, cannot be <code>null</code>.
0771: *
0772: * @param value
0773: * the value of the parameter, cannot be <code>null</code>.
0774: */
0775: private void addParameter(ElementBuilder builder, String name,
0776: String value) {
0777: ElementBuilder builderParam = new ElementBuilder("parameter");
0778: builderParam.setAttribute("name", name);
0779: builderParam.setText(value);
0780: builder.addChild(builderParam.createElement());
0781: }
0782:
0783: /**
0784: * Returns the String representation of the result.
0785: *
0786: * @param commandResult
0787: * the Element object containing the result.
0788: *
0789: * @return
0790: * the String representation of the Element.
0791: */
0792: private String serializeResult(Element commandResult) {
0793: // Store the result in a StringWriter before sending it.
0794: Writer buffer = new StringWriter(2048);
0795:
0796: // Create an XMLOutputter
0797: try {
0798: XMLOutputter xmlout = new XMLOutputter(buffer,
0799: RESPONSE_ENCODING);
0800: ElementSerializer serializer = new ElementSerializer();
0801: serializer.output(xmlout, commandResult);
0802: return buffer.toString();
0803: } catch (IOException ioe) {
0804: Log.log_3702(ioe);
0805: return null;
0806: }
0807: }
0808:
0809: /**
0810: * Translates the input using the specified XSLT.
0811: *
0812: * @param xmlInput
0813: * the XML input that should be transformed, never <code>null</code>.
0814: *
0815: * @param template
0816: * the template that should be used to transform the input XML, never <code>null</code>.
0817: *
0818: * @return
0819: * the transformed XML, never <code>null</code>.
0820: *
0821: * @throws Exception
0822: * if the transformation fails.
0823: */
0824: private String translate(String xmlInput, Templates template)
0825: throws Exception {
0826: try {
0827:
0828: // Use the template to create a transformer
0829: Transformer xformer = template.newTransformer();
0830:
0831: // Prepare the input and output files
0832: Source source = new StreamSource(new StringReader(xmlInput));
0833:
0834: // Store the result in a StringWriter before sending it.
0835: Writer buffer = new StringWriter(8192);
0836:
0837: Result result = new StreamResult(buffer);
0838:
0839: // Apply the xsl file to the source file and write the result to the output file
0840: xformer.transform(source, result);
0841:
0842: return buffer.toString();
0843: } catch (TransformerConfigurationException tcex) {
0844:
0845: // An error occurred in the XSL file
0846: Log.log_3701(tcex, "<unknown>");
0847: throw tcex;
0848: } catch (TransformerException tex) {
0849:
0850: // An error occurred while applying the XSL file
0851: // Get location of error in input file
0852: SourceLocator locator = tex.getLocator();
0853: if (locator != null) {
0854: int line = locator.getLineNumber();
0855: int col = locator.getColumnNumber();
0856: String publicId = locator.getPublicId();
0857: String systemId = locator.getSystemId();
0858: Log.log_3703(tex, String.valueOf(line), String
0859: .valueOf(col), publicId, systemId);
0860: } else {
0861: Log.log_3703(tex, "<unknown>", "<unknown>",
0862: "<unknown>", "<unknown>");
0863: }
0864: throw tex;
0865: }
0866: }
0867:
0868: /**
0869: * Gets the template to use to transform the XML.
0870: *
0871: * @param xsltUrl
0872: * the URL of the XSLT file that should be used to transform the input XML,
0873: * never <code>null</code>.
0874: *
0875: * @return
0876: * the template, never <code>null</code>.
0877: *
0878: * @throws Exception
0879: * if the URL is not found or the XSLT cannot be read correctly.
0880: */
0881: private Templates getTemplate(String xsltUrl) throws Exception {
0882:
0883: // Use the factory to create a template containing the xsl file
0884: // Load the template or get it from the cache.
0885: Templates template;
0886: if (_cacheTemplates && _templateCache.containsKey(xsltUrl)) {
0887: template = (Templates) _templateCache.get(xsltUrl);
0888: } else {
0889: try {
0890: template = _factory.newTemplates(new StreamSource(
0891: xsltUrl));
0892: if (_cacheTemplates) {
0893: _templateCache.put(xsltUrl, template);
0894: }
0895: } catch (TransformerConfigurationException tcex) {
0896: Log.log_3701(tcex, xsltUrl);
0897: throw tcex;
0898: }
0899: }
0900: return template;
0901: }
0902:
0903: /**
0904: * Gets the XSLT of the specified command.
0905: *
0906: * @param command
0907: * the command of which we want the XSLT, never <code>null</code>.
0908: *
0909: * @return
0910: * the XSLT for the command, never <code>null</code>.
0911: *
0912: * @throws IOException
0913: * if the XSLT cannot be found.
0914: */
0915: private String getCommandXSLT(String command) throws IOException {
0916:
0917: String xsltLocation = _baseXSLTDir + command + ".xslt";
0918: //httpResponse.sendRedirect(xsltLocation);
0919: InputStream inputXSLT = new URL(xsltLocation).openStream();
0920: return IOReader.readFully(inputXSLT);
0921: }
0922:
0923: /**
0924: * Gets the MIME type and the character encoding to return for the HTTP response.
0925: *
0926: * @param outputProperties
0927: * the output properties defined in the XSLT, never <code>null</code>.
0928: *
0929: * @return
0930: * the content type, never <code>null</code>.
0931: */
0932: private String getContentType(Properties outputProperties) {
0933: String mimeType = outputProperties.getProperty("media-type");
0934: if (TextUtils.isEmpty(mimeType)) {
0935: String method = outputProperties.getProperty("method");
0936: if ("xml".equals(method)) {
0937:
0938: mimeType = "text/xml";
0939: } else if ("html".equals(method)) {
0940: mimeType = "text/html";
0941: } else if ("text".equals(method)) {
0942: mimeType = "text/plain";
0943: }
0944: }
0945: String encoding = outputProperties.getProperty("encoding");
0946: if (!TextUtils.isEmpty(mimeType)
0947: && !TextUtils.isEmpty(encoding)) {
0948: mimeType += ";charset=" + encoding;
0949: }
0950: if (!TextUtils.isEmpty(mimeType)) {
0951: return mimeType;
0952: } else {
0953: return HTML_CONTENT_TYPE;
0954: }
0955: }
0956:
0957: /**
0958: * Executes the Control command.
0959: *
0960: * @param action
0961: * the action associated with the Control command, can be <code>null</code>.
0962: *
0963: * @return
0964: * the function result of the execution of the command
0965: */
0966: private FunctionResult control(String action) {
0967: if ("RemoveSessionProperties".equals(action)) {
0968: _session.removeProperties();
0969: } else if ("FlushCommandTemplateCache".equals(action)) {
0970: _templateCache.clear();
0971: } else if ("RefreshCommandTemplateCache".equals(action)) {
0972: _templateCache.clear();
0973: String xsltLocation;
0974: Iterator itRealFunctions = _api.getFunctionList()
0975: .iterator();
0976: while (itRealFunctions.hasNext()) {
0977: Function nextFunction = (Function) itRealFunctions
0978: .next();
0979: String nextCommand = nextFunction.getName();
0980: xsltLocation = _baseXSLTDir + nextCommand + ".xslt";
0981:
0982: try {
0983: Templates template = _factory
0984: .newTemplates(new StreamSource(xsltLocation));
0985: _templateCache.put(xsltLocation, template);
0986: } catch (TransformerConfigurationException tcex) {
0987: // Ignore as if the functionName include the action, it won't match a XSLT file
0988: }
0989: }
0990: Iterator itVirtualFunctions = _redirectionMap.entrySet()
0991: .iterator();
0992: while (itVirtualFunctions.hasNext()) {
0993: Map.Entry nextFunction = (Map.Entry) itVirtualFunctions
0994: .next();
0995: xsltLocation = _baseXSLTDir + nextFunction.getKey()
0996: + ".xslt";
0997: if (nextFunction.getValue().equals("-")) {
0998: try {
0999: Templates template = _factory
1000: .newTemplates(new StreamSource(
1001: xsltLocation));
1002: _templateCache.put(xsltLocation, template);
1003: } catch (TransformerConfigurationException tcex) {
1004: // Ignore as if the functionName include the action, it won't match a XSLT file
1005: }
1006: }
1007: }
1008: }
1009: return new ControlResult(_api, _session, _redirectionMap);
1010: }
1011:
1012: /**
1013: * Gets the redirection URL.
1014: *
1015: * @param xinsResult
1016: * the XINS result object that should be converted to an HTTP response,
1017: * cannot be <code>null</code>.
1018: *
1019: * @param command
1020: * the name of the command, cannot be <code>null</code>.
1021: *
1022: * @param functionName
1023: * the name of the function, cannot be <code>null</code>.
1024: *
1025: * @param xmlResult
1026: * the result of the call in case of a conditional redirection, can be <code>null</code>.
1027: *
1028: * @return
1029: * the location where the command should be redirected, or <code>null</code>
1030: * if the command should not be redirected.
1031: */
1032: private String getRedirection(FunctionResult xinsResult,
1033: String command, String functionName, String xmlResult) {
1034: String redirection = xinsResult.getParameter("redirect");
1035: if (_session.shouldLogIn()
1036: || (redirection == null && "NotLoggedIn"
1037: .equals(xinsResult.getErrorCode()))) {
1038: redirection = _loginPage + "&targetcommand=" + command;
1039: }
1040:
1041: if (redirection == null && xinsResult.getErrorCode() == null
1042: && _conditionalRedirectionMap.get(functionName) != null) {
1043: Templates conditionTemplate = (Templates) _conditionalRedirectionMap
1044: .get(functionName);
1045: try {
1046: redirection = translate(xmlResult, conditionTemplate);
1047: } catch (Exception ex) {
1048:
1049: // continue;
1050: }
1051: }
1052:
1053: if (redirection == null && xinsResult.getErrorCode() == null) {
1054: redirection = (String) _redirectionMap.get(functionName);
1055: }
1056:
1057: // No redirection for this function
1058: if (redirection == null
1059: || redirection.equals("-")
1060: || (xinsResult.getErrorCode() != null && "NotLoggedIn"
1061: .equals(xinsResult.getErrorCode()))) {
1062: return null;
1063: }
1064:
1065: // Return the location of the redirection
1066: if (redirection.equals("/")) {
1067: redirection = _defaultCommand;
1068: } else if (!redirection.startsWith("http://")
1069: && !redirection.startsWith("https://")) {
1070: redirection = "?command=" + redirection;
1071: PropertyReader parameters = xinsResult.getParameters();
1072: if (parameters != null) {
1073: Iterator parameterNames = parameters.getNames();
1074: while (parameterNames.hasNext()) {
1075: String nextParameter = (String) parameterNames
1076: .next();
1077: if (!"redirect".equals(nextParameter)) {
1078: redirection += "&" + nextParameter + '='
1079: + parameters.get(nextParameter);
1080: }
1081: }
1082: }
1083: }
1084: return redirection;
1085: }
1086:
1087: /**
1088: * Initializes the redirections of the commands.
1089: *
1090: * @param bootstrapProperties
1091: * the bootstrap properties, cannot be <code>null</code>.
1092: */
1093: private void initRedirections(PropertyReader bootstrapProperties) {
1094:
1095: TreeMap conditionalRedirectionProperties = new TreeMap();
1096:
1097: // Get the commands automatically redirected to another one
1098: Iterator itProperties = bootstrapProperties.getNames();
1099: while (itProperties.hasNext()) {
1100: String nextProp = (String) itProperties.next();
1101: if (nextProp.startsWith("xinsff.redirect.")) {
1102: String command = nextProp.substring(16);
1103: String redirectionPage = bootstrapProperties
1104: .get(nextProp);
1105: // TODO the condition should have the same order as in the XML?
1106: int conditionalPos = command.indexOf('[');
1107: if (conditionalPos != -1) {
1108: conditionalRedirectionProperties.put(command,
1109: redirectionPage);
1110: } else {
1111: _redirectionMap.put(command, redirectionPage);
1112: }
1113: }
1114: }
1115:
1116: // Create the conditional map
1117: String startXSLT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1118: + "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n"
1119: + "<xsl:output omit-xml-declaration=\"yes\" />\n"
1120: + "<xsl:template match=\"commandresult\">\n"
1121: + "<xsl:choose>\n";
1122: Iterator itConditions = conditionalRedirectionProperties
1123: .entrySet().iterator();
1124: String currentCommand = null;
1125: String xsltText = null;
1126: while (itConditions.hasNext()) {
1127:
1128: // Parse the line
1129: Map.Entry nextCondition = (Map.Entry) itConditions.next();
1130: String nextKey = (String) nextCondition.getKey();
1131: int conditionPos = nextKey.indexOf('[');
1132: String command = nextKey.substring(0, conditionPos);
1133: String condition = nextKey.substring(conditionPos + 1,
1134: nextKey.length() - 1);
1135: String redirectionPage = (String) nextCondition.getValue();
1136:
1137: // Create the template object and store it
1138: if (currentCommand != null
1139: && !currentCommand.equals(command)) {
1140: finishConditionalTemplate(command, xsltText);
1141: currentCommand = null;
1142: }
1143:
1144: // Start a new template as it is a new command
1145: if (currentCommand == null) {
1146: xsltText = startXSLT;
1147: currentCommand = command;
1148: }
1149:
1150: // Add the condition in the XSL choose
1151: xsltText += "<xsl:when test=\"" + condition
1152: + "\"><xsl:text>" + redirectionPage
1153: + "</xsl:text></xsl:when>\n";
1154:
1155: // Close the last condition
1156: if (!itConditions.hasNext()) {
1157: finishConditionalTemplate(command, xsltText);
1158: }
1159: }
1160: }
1161:
1162: /**
1163: * Finishes the creation of the XSLT, creates the {@link Templates} object
1164: * and stores it in the map.
1165: *
1166: * @param command
1167: * the command to store, cannot be <code>null</code>.
1168: *
1169: * @param currentXSLT
1170: * the XSLT created before, cannot be <code>null</code>.
1171: */
1172: private void finishConditionalTemplate(String command,
1173: String currentXSLT) {
1174: String defaultRedirection = (String) _redirectionMap
1175: .get(command);
1176: if (defaultRedirection == null) {
1177: defaultRedirection = "-";
1178: }
1179: String xsltText = currentXSLT;
1180: xsltText += "<xsl:when test=\"not(param[@name='error.type'])\"><xsl:text>"
1181: + defaultRedirection + "</xsl:text></xsl:when>\n";
1182: xsltText += "<xsl:otherwise><xsl:text>-</xsl:text></xsl:otherwise>\n";
1183: xsltText += "</xsl:choose></xsl:template></xsl:stylesheet>";
1184: try {
1185: StringReader conditionXSLT = new StringReader(xsltText);
1186: Templates conditionTemplate = _factory
1187: .newTemplates(new StreamSource(conditionXSLT));
1188: _conditionalRedirectionMap.put(command, conditionTemplate);
1189: } catch (TransformerConfigurationException tcex) {
1190: Log.log_3701(tcex, "conditional redirection for " + command
1191: + " command");
1192: }
1193: }
1194:
1195: /**
1196: * Displays the transformation error.
1197: *
1198: * @param transformException
1199: * The exception that occured during the transformation, cannot be <code>null</code>.
1200: *
1201: * @param httpResponse
1202: * where to send the response, cannot be <code>null</code>.
1203: *
1204: * @param httpRequest
1205: * the request of the user, cannot be <code>null</code>.
1206: *
1207: * @throws IOException
1208: * if this transformation also fails for any reason.
1209: */
1210: private void showError(Exception transformException,
1211: HttpServletResponse httpResponse,
1212: HttpServletRequest httpRequest) throws IOException {
1213: try {
1214: FunctionResult errorResult = new ErrorResult(
1215: transformException, httpRequest);
1216: if (_templateError == null) {
1217: if (_errorPage == null) {
1218: try {
1219: StringReader errorXSLT = new StringReader(
1220: ErrorResult.getDefaultErrorTemplate());
1221: _templateError = _factory
1222: .newTemplates(new StreamSource(
1223: errorXSLT));
1224: } catch (TransformerConfigurationException tcex) {
1225: Log.log_3701(tcex, "error");
1226: }
1227: } else {
1228: _templateError = getTemplate(_baseXSLTDir
1229: + _errorPage + ".xslt");
1230: }
1231: }
1232: Element commandResult = createXMLResult(httpRequest,
1233: errorResult);
1234: String commandResultXML = serializeResult(commandResult);
1235: String resultHTML = translate(commandResultXML,
1236: _templateError);
1237: String contentType = getContentType(_templateError
1238: .getOutputProperties());
1239: PrintWriter out = httpResponse.getWriter();
1240: httpResponse.setContentType(contentType);
1241: httpResponse
1242: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1243: out.print(resultHTML);
1244: out.close();
1245: } catch (Exception ex) {
1246: if (ex instanceof IOException) {
1247: throw (IOException) ex;
1248: } else if (ex instanceof RuntimeException) {
1249: throw (RuntimeException) ex;
1250: } else {
1251: throw new IOException(ex.getMessage());
1252: }
1253: }
1254: }
1255:
1256: /**
1257: * Gets the real parameter name.
1258: *
1259: * @param receivedParameter
1260: * the name of the parameter as received.
1261: *
1262: * @param functionName
1263: * the name of the function.
1264: *
1265: * @return
1266: * the name of the parameter as specified in the function.
1267: *
1268: * @deprecated
1269: * no mapping should be needed and the forms should send directly the correct parameters.
1270: */
1271: private String getRealParameter(String receivedParameter,
1272: String functionName) {
1273: String flatParameter = receivedParameter;
1274: if (receivedParameter.indexOf("_") != -1) {
1275: flatParameter = TextUtils.removeCharacter('_',
1276: receivedParameter);
1277: }
1278: try {
1279: FunctionSpec function = _api.getAPISpecification()
1280: .getFunction(functionName);
1281: Set parametersSet = function.getInputParameters().keySet();
1282: if (parametersSet.contains(receivedParameter)) {
1283: return receivedParameter;
1284: }
1285: Iterator itParameters = parametersSet.iterator();
1286: while (itParameters.hasNext()) {
1287: String nextParameterName = (String) itParameters.next();
1288: if (nextParameterName.equalsIgnoreCase(flatParameter)) {
1289: return nextParameterName;
1290: }
1291: }
1292: } catch (Exception ex) {
1293:
1294: // No function defined for this call, continue
1295: }
1296: return receivedParameter;
1297: }
1298:
1299: /**
1300: * Gets the original passed parameter name.
1301: *
1302: * @param parameter
1303: * the name of the parameter as specified in the function, cannot be <code>null</code>.
1304: *
1305: * @return
1306: * the name of the parameter as received.
1307: *
1308: * @deprecated
1309: * no mapping should be needed and the forms should send directly the correct parameters.
1310: */
1311: private String getOriginalParameter(String parameter) {
1312: Map inputs = (Map) _session.getProperty("_inputs");
1313: Iterator itParameterNames = inputs.keySet().iterator();
1314: while (itParameterNames.hasNext()) {
1315: String nextParam = (String) itParameterNames.next();
1316: String flatParam = TextUtils
1317: .removeCharacter('_', nextParam);
1318: if (parameter.equalsIgnoreCase(flatParam)) {
1319: return nextParam;
1320: }
1321: }
1322: return parameter;
1323: }
1324: }
|