/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. licenses this file to You 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 com.esri.gpt.catalog.gxe;
import com.esri.gpt.catalog.schema.MetadataDocument;
import com.esri.gpt.catalog.schema.Schema;
import com.esri.gpt.catalog.schema.SchemaException;
import com.esri.gpt.catalog.schema.Schemas;
import com.esri.gpt.catalog.schema.UnrecognizedSchemaException;
import com.esri.gpt.framework.context.BaseServlet;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.jsf.FacesContextBroker;
import com.esri.gpt.framework.jsf.MessageBroker;
import com.esri.gpt.framework.util.Val;
import com.esri.gpt.framework.xml.DomUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.fileupload.FileItem;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* The Geoportal XML editor servlet.
*/
@SuppressWarnings("serial")
public class GxeServlet extends BaseServlet {
/** class variables ========================================================= */
/** The Logger. */
private static Logger LOGGER = Logger.getLogger(GxeServlet.class.getName());
/** methods ================================================================= */
/**
* Executes a request.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @throws Exception if a processing exception occurs
*/
@Override
protected void execute(HttpServletRequest request,
HttpServletResponse response,
RequestContext context)
throws Exception {
// determine the request type and execute
try {
if (request.getRequestURI() != null) {
String sLcUri = request.getRequestURI().toLowerCase();
if (sLcUri.endsWith("/definition/type")) {
this.executeDefinitionType(request,response,context);
} else if (sLcUri.endsWith("/definition/types")) {
this.executeDefinitionTypes(request,response,context);
} else if (sLcUri.endsWith("/echo/attachment")) {
this.executeEchoAttachment(request,response,context);
} else if (sLcUri.endsWith("/i18n")) {
this.executeI18N(request,response,context);
} else if (sLcUri.endsWith("/interrogate")) {
this.executeInterrogate(request,response,context);
} else if (sLcUri.endsWith("/interrogate/details")) {
this.executeInterrogateDetails(request,response,context,true);
} else if (sLcUri.endsWith("/interrogate/multipart")) {
this.executeInterrogate(request,response,context);
// this is for backward compatibility,
// should be using /definition/type
} else if (sLcUri.endsWith("/definition")) {
this._executeDefinitionRequest(request,response,context);
// this is for backward compatibility,
// should be using /interrogate/details
} else if (sLcUri.endsWith("/details")) {
this.executeInterrogateDetails(request,response,context,false);
// this is for backward compatibility,
// should be using /definition/types
} else if (sLcUri.endsWith("/types")) {
this.executeDefinitionTypes(request,response,context);
}
}
} finally {
context.onExecutionPhaseCompleted();
}
}
/**
* Handles a request to return the editor definition for
* a supplied schema key or location.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @throws Exception if a processing exception occurs
*/
private void executeDefinitionType(HttpServletRequest request,
HttpServletResponse response,
RequestContext context)
throws Exception {
String sMimeType = "application/json";
String sResponse = "";
String sCallback = "";
MessageBroker msgBroker = null;
try {
// determine the response format
String f = Val.chkStr(request.getParameter("f"));
if (f.equalsIgnoreCase("pjson")) {
sMimeType = "text/plain";
}
sCallback = Val.chkStr(request.getParameter("callback"));
// determine the definition
GxeDefinition definition = null;
String key = Val.chkStr(request.getParameter("key"));
String loc = Val.chkStr(request.getParameter("loc"));
if (key.length() > 0) {
Schemas schemas = context.getCatalogConfiguration().getConfiguredSchemas();
Schema schema = schemas.get(key);
if (schema == null) {
throw new SchemaException("Unsupported schema key.");
} else {
definition = schema.getGxeEditorDefinition();
}
} else if (loc.length() > 0) {
definition = new GxeDefinition();
definition.setFileLocation(loc);
}
String sCfg = "";
if (definition != null) {
msgBroker = this.getMessageBroker(request,response,context,msgBroker);
sCfg = this.generateDefinition(request,response,context,msgBroker,definition);
}
// set the response
if ((sCfg != null) && (sCfg.length() > 0)) {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\r\n\"cfgDefinition\":").append(sCfg);
sb.append("\r\n}");
sResponse = sb.toString();
} else {
throw new SchemaException("Unsupported schema type.");
}
} catch (Throwable t) {
if (!(t instanceof SchemaException)) {
LOGGER.log(Level.WARNING,"Error processing request.",t);
}
sResponse = this.generateJsonError(request,response,context,msgBroker,t);
}
// write the response
//LOGGER.finest("gxeResponse:\n"+sResponse);
if ((sResponse != null) && (sResponse.length() > 0)) {
if ((sCallback != null) && (sCallback.length() > 0)) {
sResponse = sCallback+"("+sResponse+")";
}
writeCharacterResponse(response,sResponse,"UTF-8",sMimeType+";charset=UTF-8");
}
}
/**
* Handles a request for the list of defined editor types.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @throws Exception if a processing exception occurs
*/
private void executeDefinitionTypes(HttpServletRequest request,
HttpServletResponse response,
RequestContext context)
throws Exception {
String sMimeType = "application/json";
String sResponse = "";
String sCallback = "";
MessageBroker msgBroker = null;
try {
sCallback = Val.chkStr(request.getParameter("callback"));
// make the list of defined editor types
StringBuilder sb = new StringBuilder();
sb.append("{\"types\": [");
Schemas schemas = context.getCatalogConfiguration().getConfiguredSchemas();
if (schemas != null) {
int n = 0;
msgBroker = this.getMessageBroker(request,response,context,msgBroker);
for (Schema schema: schemas.values()) {
if (schema.getEditable()) {
GxeDefinition definition = schema.getGxeEditorDefinition();
if (definition != null) {
String key = schema.getKey();
String label = null;
if (schema.getLabel() != null) {
String resKey = schema.getLabel().getResourceKey();
if ((resKey != null) && (resKey.length() > 0)) {
label = msgBroker.retrieveMessage(resKey);
}
}
if ((label == null) || (label.length() == 0)) {
label = key;
}
n++;
if (n > 1) sb.append(",");
sb.append("\r\n\t");
sb.append("{\"key\": \"").append(Val.escapeStrForJson(key)).append("\"");
sb.append(", \"label\": \"").append(Val.escapeStrForJson(label)).append("\"}");
}
}
}
}
sb.append("\r\n]}");
sResponse = sb.toString();
} catch (Throwable t) {
LOGGER.log(Level.WARNING,"Error processing request.",t);
sResponse = this.generateJsonError(request,response,context,msgBroker,t);
}
// write the response
//LOGGER.finest("gxeResponse:\n"+sResponse);
if ((sResponse != null) && (sResponse.length() > 0)) {
if ((sCallback != null) && (sCallback.length() > 0)) {
sResponse = sCallback+"("+sResponse+")";
}
writeCharacterResponse(response,sResponse,"UTF-8",sMimeType+";charset=UTF-8");
}
}
/**
* Handles a request to return a posted XML document as
* a content disposition attachment.
* <br/>The posted mime-type should be: application/x-www-form-urlencoded
* <br/>The posted URL parameter name should be: xml
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @throws Exception if a processing exception occurs
*/
private void executeEchoAttachment(HttpServletRequest request,
HttpServletResponse response,
RequestContext context)
throws Exception {
String sResponse = "";
String sFilename = "";
try {
// read the posted xml
String sXml = this.readPostedXml(request,response,context);
if ((sXml != null) && (sXml.length() > 0)) {
sResponse = sXml;
sFilename = Val.chkStr(request.getParameter("filename"));
if (sFilename.length() == 0) sFilename = "metadata.xml";
} else {
sFilename = "error.txt";
sResponse = "Error: The posted XML was empty.";
}
} catch (Throwable t) {
LOGGER.log(Level.WARNING,"Error processing request.",t);
String sMsg = Val.chkStr(t.getMessage());
if (sMsg.length() == 0) sMsg = t.toString();
sFilename = "error.xml";
sResponse = "Error: "+sMsg;
}
// write the response
//LOGGER.finest("gxeResponse:\n"+sResponse);
response.setContentType("APPLICATION/OCTET-STREAM; charset=UTF-8");
response.setHeader("Content-Disposition","attachment; filename=\""+sFilename+"\"");
writeCharacterResponse(response,sResponse,"UTF-8","APPLICATION/OCTET-STREAM; charset=UTF-8");
}
/**
* Handles a request to lookup a set of localized strings.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @throws Exception if a processing exception occurs
*/
private void executeI18N(HttpServletRequest request,
HttpServletResponse response,
RequestContext context)
throws Exception {
String sMimeType = "application/json";
String sResponse = "";
String sCallback = "";
MessageBroker msgBroker = null;
try {
// determine the response format
String f = Val.chkStr(request.getParameter("f"));
if (f.equalsIgnoreCase("pjson")) {
sMimeType = "text/plain";
}
sCallback = Val.chkStr(request.getParameter("callback"));
msgBroker = this.getMessageBroker(request,response,context,msgBroker);
// determine the property keys
String sKeys = Val.chkStr(request.getParameter("keys"));
String[] aKeys = sKeys.split(",");
// lookup the strings
StringBuilder sb = new StringBuilder();
sb.append("{\"i18n\": {");
int n = 0;
if ((aKeys != null) && (aKeys.length > 0)) {
for (String sKey: aKeys) {
String sValue = msgBroker.retrieveMessage(sKey);
n++;
if (n > 1) sb.append(",");
sb.append("\r\n\t");
sb.append("\"").append(Val.escapeStrForJson(sKey)).append("\": ");
sb.append("\"").append(Val.escapeStrForJson(sValue)).append("\"");
}
}
sb.append("\r\n}}");
sResponse = sb.toString();
} catch (Throwable t) {
LOGGER.log(Level.WARNING,"Error processing request.",t);
sResponse = this.generateJsonError(request,response,context,msgBroker,t);
}
// write the response
//LOGGER.finest("gxeResponse:\n"+sResponse);
if ((sResponse != null) && (sResponse.length() > 0)) {
if ((sCallback != null) && (sCallback.length() > 0)) {
sMimeType = "text/plain";
sResponse = sCallback+"("+sResponse+")";
}
writeCharacterResponse(response,sResponse,"UTF-8",sMimeType+";charset=UTF-8");
}
}
/**
* Handles a request to interrogate the editor definition for
* an uploaded XML document.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @throws Exception if a processing exception occurs
*/
private void executeInterrogate(HttpServletRequest request,
HttpServletResponse response,
RequestContext context)
throws Exception {
String sMimeType = "application/json";
String sResponse = "";
boolean isIE = false;
boolean bWrap = false;
MessageBroker msgBroker = null;
try {
isIE = Val.chkBool(request.getParameter("isIE"),false);
// interrogate the posted XMl, generate the definition
bWrap = Val.chkBool(request.getParameter("wrap"),false);
String sXml = this.readPostedXml(request,response,context);
Schema schema = this.interrogateSchema(context,sXml);
String sCfg = "";
GxeDefinition definition = schema.getGxeEditorDefinition();
if (definition != null) {
msgBroker = this.getMessageBroker(request,response,context,msgBroker);
sCfg = this.generateDefinition(request,response,context,msgBroker,definition);
}
// set the response
if ((sCfg != null) && (sCfg.length() > 0)) {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\r\n\"cfgDefinition\":").append(sCfg);
sb.append(",\r\n\"xml\":\"").append(
Val.escapeStrForJson(schema.getActiveDocumentXml())).append("\"");
sb.append("\r\n}");
sResponse = sb.toString();
} else {
throw new SchemaException("Unsupported XML type.");
}
} catch (Throwable t) {
if (!(t instanceof SchemaException)) {
LOGGER.log(Level.WARNING,"Error processing request.",t);
}
sResponse = this.generateJsonError(request,response,context,msgBroker,t);
}
// write the response
//LOGGER.finest("gxeResponse:\n"+sResponse);
if ((sResponse != null) && (sResponse.length() > 0)) {
if(isIE){
sMimeType = "text/html";
sResponse = "<script type=\"text/javascript\">var gxeImportFileResponse="+sResponse+";</script>";
}
// responseObject= is required for the
// HTML5 based dojox.form.Uploader 1.6
else if (bWrap) sResponse = "responseObject="+sResponse;
writeCharacterResponse(response,sResponse,"UTF-8",sMimeType+";charset=UTF-8");
}
}
/**
* Handles a request for a details view of an XML document.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @throws Exception if a processing exception occurs
*/
private void executeInterrogateDetails(HttpServletRequest request,
HttpServletResponse response,
RequestContext context,
boolean defaultToJson)
throws Exception {
String sMimeType = "application/json";
String sFormat = "json";
String sResponse = "";
MessageBroker msgBroker = null;
try {
// determine the response format
if (!defaultToJson) {
sFormat = "htmlFragment";
}
String f = Val.chkStr(request.getParameter("f"));
if (f.equalsIgnoreCase("htmlFragment")) {
sFormat = "htmlFragment";
} else if (f.equalsIgnoreCase("json")) {
sFormat = "json";
} else if (f.equalsIgnoreCase("pjson")) {
sFormat = "json";
}
if (!sFormat.equals("json")) {
sMimeType = "text/plain";
}
// interrogate the posted XMl, generate the details
String sXml = this.readPostedXml(request,response,context);
Schema schema = this.interrogateSchema(context,sXml);
msgBroker = this.getMessageBroker(request,response,context,msgBroker);
String sDetails = this.generateDetails(request,response,context,msgBroker,schema);
sDetails = Val.chkStr(sDetails);
// set the response
if (sFormat.equals("json")) {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\"details\": \"").append(Val.escapeStrForJson(sDetails)).append("\"");
sb.append("}");
sResponse = sb.toString();
} else {
sResponse = sDetails;
}
} catch (Throwable t) {
if (!(t instanceof SchemaException)) {
LOGGER.log(Level.WARNING,"Error processing request.",t);
}
if (sFormat.equals("json")) {
sResponse = this.generateJsonError(request,response,context,msgBroker,t);
} else {
String sMsg = Val.chkStr(t.getMessage());
if (sMsg.length() == 0) sMsg = t.toString();
sResponse = Val.escapeXmlForBrowser(sMsg);
}
}
// write the response
// LOGGER.finest("gxeResponse:\n"+sResponse);
if ((sResponse != null) && (sResponse.length() > 0)) {
writeCharacterResponse(response,sResponse,"UTF-8",sMimeType+";charset=UTF-8");
}
}
/**
* Generates the JSON editor definition.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @param msgBroker the message broker
* @param definition the GXE definition
* @return the JSON definition
* @throws Exception if a processing exception occurs
*/
private String generateDefinition(HttpServletRequest request,
HttpServletResponse response,
RequestContext context,
MessageBroker msgBroker,
GxeDefinition definition)
throws Exception {
GxeContext gxeContext = new GxeContext();
gxeContext.setMessageBroker(msgBroker);
if (definition.getRootElement() == null) {
GxeLoader loader = new GxeLoader();
loader.loadDefinition(gxeContext,definition);
}
GxeJsonSerializer serializer = new GxeJsonSerializer();
return serializer.asJson(gxeContext,definition);
}
/**
* Generates a details view for an interrogated schema.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @param msgBroker the message broker
* @param schema the interrogated schema
* @return the details
* @throws Exception if a processing exception occurs
*/
private String generateDetails(HttpServletRequest request,
HttpServletResponse response,
RequestContext context,
MessageBroker msgBroker,
Schema schema)
throws Exception {
String sXslt = Val.chkStr(schema.getDetailsXslt());
if (sXslt.length() > 0) {
MetadataDocument document = new MetadataDocument();
return document.transformDetails(
schema.getActiveDocumentXml(),sXslt,msgBroker);
}
return null;
}
/**
* Generates a JSON based error object.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @param msgBroker the message broker
* @param t the exception
* @return the JSON string
*/
private String generateJsonError(HttpServletRequest request,
HttpServletResponse response,
RequestContext context,
MessageBroker msgBroker,
Throwable t) {
String sMsg = Val.chkStr(t.getMessage());
if (sMsg.length() == 0) sMsg = t.toString();
// TODO localized messages here
if (sMsg.contains("Unrecognized metadata schema.")) {
} else if (sMsg.contains("Unable to parse document.")) {
} else if (sMsg.contains("Unsupported XML type.")) {
}
StringBuilder sb = new StringBuilder();
sb.append("{\"error\":{");
sb.append("\"message\": \"").append(Val.escapeStrForJson(sMsg)).append("\"");
sb.append("}}");
return sb.toString();
}
/**
* Ensures the existence of a message broker.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @param msgBroker a message broker reference
* (if null then a new message broker will be generated)
* @return the message broker
*/
private MessageBroker getMessageBroker(HttpServletRequest request,
HttpServletResponse response,
RequestContext context,
MessageBroker msgBroker) {
// TODO check for lang parameter
if (msgBroker != null) {
return msgBroker;
} else {
FacesContextBroker fcb = new FacesContextBroker(request,response);
return fcb.extractMessageBroker();
}
}
/**
* Interrogates a schema based upon a posted XML document.
* @param context the request context
* @param xml the XML string
* @return the corresponding schema
* @throws Exception if a processing exception occurs
*/
private Schema interrogateSchema(RequestContext context, String xml)
throws Exception {
String sXml = Val.chkStr(Val.removeBOM(xml));
if (sXml.length() == 0) {
throw new UnrecognizedSchemaException("Unrecognized metadata schema.");
} else {
MetadataDocument document = new MetadataDocument();
Schema schema = document.prepareForView(context,sXml);
return schema;
}
}
/**
* Reads a posted XML document.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @throws Exception if a processing exception occurs
*/
private String readPostedXml(HttpServletRequest request,
HttpServletResponse response,
RequestContext context)
throws Exception {
String sXml = "";
String sContentType = Val.chkStr(request.getContentType());
if (sContentType.toLowerCase().startsWith("multipart/form-data;")) {
String sMultipartAttribute = "uploadedfiles[]";
Object oFile = request.getAttribute(sMultipartAttribute);
if (oFile == null) {
sMultipartAttribute = "uploadedfile";
oFile = request.getAttribute(sMultipartAttribute);
}
if ((oFile != null) && (oFile instanceof FileItem)) {
FileItem item = (FileItem)oFile;
sXml = Val.chkStr(Val.removeBOM(item.getString("UTF-8")));
}
} else if (sContentType.toLowerCase().startsWith("application/x-www-form-urlencoded")) {
String sFormUrlParamater = "xml";
sXml = Val.chkStr(Val.removeBOM(request.getParameter(sFormUrlParamater)));
} else {
sXml = Val.chkStr(Val.removeBOM(this.readInputCharacters(request)));
}
/*
// check for multipart/form-data
String sMultipartAttribute = "uploadedfiles[]";
Object oFile = request.getAttribute(sMultipartAttribute);
if ((oFile != null) && (oFile instanceof FileItem)) {
FileItem item = (FileItem)oFile;
sXml = Val.chkStr(Val.removeBOM(item.getString("UTF-8")));
} else {
// check for application/x-www-form-urlencoded
String sFormUrlParamater = "xml";
sXml = Val.chkStr(request.getParameter(sFormUrlParamater));
if (sXml == null) {
// check the raw post body
sXml = this.readInputCharacters(request);
}
}
*/
return sXml;
}
/**
* Handles a request for an editor definition.
* <br/>Original method, should be deprecated.
* @param request the HTTP servlet request
* @param response the HTTP servlet response
* @param context the request context
* @throws Exception if a processing exception occurs
*/
private void _executeDefinitionRequest(HttpServletRequest request,
HttpServletResponse response,
RequestContext context)
throws Exception {
// initialize
String mimeType = "text/plain";
String sResponse = "";
// handle a request for an editor definition
try {
GxeContext gxeContext = new GxeContext();
FacesContextBroker fcb = new FacesContextBroker(request,response);
gxeContext.setMessageBroker(fcb.extractMessageBroker());
GxeDefinition definition = null;
String key = Val.chkStr(request.getParameter("key"));
String loc = Val.chkStr(request.getParameter("loc"));
if (key.length() > 0) {
Schemas schemas = context.getCatalogConfiguration().getConfiguredSchemas();
Schema schema = schemas.get(key);
if (schema != null) {
definition = schema.getGxeEditorDefinition();
}
} else if (loc.length() > 0) {
definition = new GxeDefinition();
definition.setFileLocation(loc);
} else {
String xml = null;
try {
xml = this.readInputCharacters(request);
} catch (IOException e) {
LOGGER.log(Level.WARNING,"Error processing request.",e);
throw new ServletException("400: IOException while reading request body.");
}
xml = Val.chkStr(Val.removeBOM(xml));
if (xml.length() > 0) {
MetadataDocument document = new MetadataDocument();
Schema schema = document.prepareForView(context,xml);
if (schema != null) {
definition = schema.getGxeEditorDefinition();
}
}
}
if (definition != null) {
if (definition.getRootElement() == null) {
GxeLoader loader = new GxeLoader();
loader.loadDefinition(gxeContext,definition);
}
GxeJsonSerializer serializer = new GxeJsonSerializer();
sResponse = serializer.asJson(gxeContext,definition);
String sCallbackParam = Val.chkStr(request.getParameter("callback"));
if (sCallbackParam.length() > 0) {
sResponse = sCallbackParam+"("+sResponse+")";
}
}
} catch (SchemaException e) {
LOGGER.log(Level.WARNING,"Error processing request.",e);
String sMsg = e.toString();
if (sMsg.contains("Unrecognized metadata schema.")) {
throw new ServletException("409: Unrecognized metadata schema.");
} else if (sMsg.contains("Unable to parse document.")) {
throw new ServletException("409: Unable to parse document as XML.");
} else {
throw new ServletException("409: Unable process request.");
}
} catch (Exception e) {
throw e;
} finally {
context.onExecutionPhaseCompleted();
}
// write the response
LOGGER.finest("gxeResponse:\n"+sResponse);
if ((sResponse != null) && (sResponse.length() > 0)) {
writeCharacterResponse(response,sResponse,"UTF-8",mimeType+";charset=UTF-8");
}
}
}