Java tutorial
/* * * $Revision$ $Date$ * * This file is part of *** M y C o R e *** * See http://www.mycore.de/ for details. * * This program is free software; you can use it, redistribute it * and / or modify it under the terms of the GNU General Public License * (GPL) as published by the Free Software Foundation; either version 2 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program, in a file called gpl.txt or license.txt. * If not, write to the Free Software Foundation Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA */ package org.mycore.frontend.editor; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.TransformerException; import org.apache.commons.fileupload.FileItem; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.filter.ElementFilter; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; import org.mycore.common.MCRException; import org.mycore.common.MCRSession; import org.mycore.common.MCRSessionMgr; import org.mycore.common.content.MCRJDOMContent; import org.mycore.common.xml.MCRURIResolver; import org.mycore.frontend.MCRFrontendUtil; import org.mycore.frontend.editor.postprocessor.MCREditorPostProcessor; import org.mycore.frontend.servlets.MCRServlet; import org.mycore.frontend.servlets.MCRServletJob; import org.xml.sax.SAXException; /** * This servlet handles form submissions from MyCoRe XML Editor pages and * converts the submitted data into a JDOM XML document for further processing. * It can also handle file uploads. * * @author Frank Ltzenkirchen * @version $Revision$ $Date: 2010-11-04 12:22:36 +0100 (Do, 04 Nov * 2010) $ */ public class MCREditorServlet extends MCRServlet { private static final long serialVersionUID = 1L; private final static Logger LOGGER = LogManager.getLogger(MCREditorServlet.class); public void doGetPost(MCRServletJob job) throws ServletException, IOException, TransformerException, SAXException { MCRRequestParameters parms = new MCRRequestParameters(job.getRequest()); String action = parms.getParameter("_action"); if ("show.popup".equals(action)) { processShowPopup(job); } else if ("submit".equals(action)) { processSubmit(job, parms); } else if ("end.subselect".equals(action)) { processEndSubSelect(job, parms); } else if ("include".equals(action)) { includeEditorFromXSL(job); } else { sendBadRequest(job); } } private void sendBadRequest(MCRServletJob job) throws IOException { job.getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST); } private void includeEditorFromXSL(MCRServletJob job) throws IOException { HttpServletRequest req = job.getRequest(); HttpServletResponse res = job.getResponse(); String sessionID = job.getRequest().getParameter("XSL.editor.session.id"); Element editorResolved = null; if (sessionID != null) { editorResolved = MCREditorSessionCache.instance().getEditorSession(sessionID).getXML(); } if (editorResolved == null || sessionID == null) { Map parameters = job.getRequest().getParameterMap(); String ref = req.getParameter("_ref"); String uri = req.getParameter("_uri"); boolean validate = "true".equals(req.getParameter("_validate")); editorResolved = startSession(parameters, ref, uri, validate); } getLayoutService().sendXML(req, res, new MCRJDOMContent((Element) editorResolved.clone())); } /** * Shows a help popup window */ private void processShowPopup(MCRServletJob job) throws IOException, TransformerException, SAXException { String sessionID = job.getRequest().getParameter("_session"); String ref = job.getRequest().getParameter("_ref"); LOGGER.debug("Editor session " + sessionID + " show popup " + ref); Element editor = MCREditorSessionCache.instance().getEditorSession(sessionID).getXML(); Element popup = MCREditorDefReader.findElementByID(ref, editor); Element clone = (Element) popup.clone(); getLayoutService().doLayout(job.getRequest(), job.getResponse(), new MCRJDOMContent(clone)); } /** * Replaces editor elements in static webpage with complete editor * definition. * * @param request * the current MCRServletJob * @param uri * the uri of the static XML file containing the editor * @param xml * the complete XML document of the static webpage */ public static void replaceEditorElements(HttpServletRequest request, String uri, Document xml) { String sessionID = request.getParameter("XSL.editor.session.id"); List<Element> editors = new ArrayList<Element>(); Iterator it = xml.getDescendants(new ElementFilter("editor")); while (it.hasNext()) { editors.add((Element) it.next()); } for (Element editor : editors) { Element editorResolved = null; if (sessionID != null) { MCREditorSession editorSession = MCREditorSessionCache.instance().getEditorSession(sessionID); if (editorSession == null) { throw new MCRException("Editor session is invalid:" + sessionID); } editorResolved = editorSession.getXML(); } if (sessionID == null || editorResolved == null) { Map parameters = request.getParameterMap(); boolean validate = "true".equals(editor.getAttributeValue("validate", "false")); String ref = editor.getAttributeValue("id"); editorResolved = startSession(parameters, ref, uri, validate); } String clazz1 = editor.getAttributeValue("class"); String clazz2 = editorResolved.getAttributeValue("class"); editor.removeContent(); editor.addContent(editorResolved.cloneContent()); editor.setAttribute("session", editorResolved.getAttributeValue("session")); editor.setAttribute("class", (clazz1 == null ? clazz2 : clazz1)); } } /** * Starts a new editor session in webpage */ private static Element startSession(Map parameters, String ref, String uri, boolean validate) { LOGGER.debug("Editor start editor session from " + ref + "@" + uri); MCRParameters mp = new MCRParameters(parameters); Element param = getTargetParameters(parameters); Element editor = new MCREditorDefReader(uri, ref, validate, mp).getEditor(); MCREditorSession mcrEditor = new MCREditorSession(editor, mp); MCREditorSessionCache.instance().storeEditorSession(mcrEditor); if (param != null) { editor.addContent(param); } readEditorInput(editor, mcrEditor); return editor; } private static void readEditorInput(Element editor, MCREditorSession mcrEditor) { String sourceURI = mcrEditor.getSourceURI(); LOGGER.info("Editor reading XML input from " + sourceURI); Element input = MCRURIResolver.instance().resolve(sourceURI); if (LOGGER.isDebugEnabled()) { LOGGER.info(new XMLOutputter(Format.getPrettyFormat()).outputString(input)); } MCREditorSubmission sub = new MCREditorSubmission(input, editor); MCREditorDefReader.fixConditionedVariables(editor); editor.addContent(sub.buildInputElements()); editor.addContent(sub.buildRepeatElements()); } private static Element getTargetParameters(Map parameters) { Element tps = new Element("target-parameters"); for (Object o : parameters.keySet()) { String parameterName = (String) o; String[] parameterValues = (String[]) parameters.get(parameterName); if (parameterValues == null) continue; if (parameterValues.length == 0) continue; if (parameterName.startsWith("XSL.target.param.")) { for (String parameterValue : parameterValues) { if (parameterValue.isEmpty()) continue; String[] parts = parameterValue.split("=", 2); String name = parts[0].trim(); String value = parts[1].trim(); tps.addContent(buildTargetParameter(name, value)); } } else if (!parameterName.startsWith("XSL.")) { for (String parameterValue : parameterValues) tps.addContent(buildTargetParameter(parameterName, parameterValue)); } } return tps; } private static Element buildTargetParameter(String name, String value) { LOGGER.debug("Editor target parameter " + name + "=" + value); Element tp = new Element("target-parameter"); tp.setAttribute("name", name); tp.addContent(value); return tp; } private void processEndSubSelect(MCRServletJob job, MCRRequestParameters parms) throws java.io.IOException { String root = "root"; List variables = new ArrayList(); Enumeration e = job.getRequest().getParameterNames(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); String value = job.getRequest().getParameter(name); if (!name.startsWith("_var_")) { continue; } if (name.length() > 5) { name = root + "/" + name.substring(5); if (name.contains("[@")) { name = name.replace("[@", MCREditorSubmission.ATTR_SEP); name = name.replace("='", MCREditorSubmission.ATTR_SEP); name = name.replace(MCREditorSubmission.BLANK, MCREditorSubmission.BLANK_ESCAPED); name = name.replace("']", ""); } } else { name = root; } variables.add(new MCREditorVariable(name, value)); } // Remove subselect parameters from current session MCRSession mcrSession = MCRSessionMgr.getCurrentSession(); mcrSession.deleteObject("XSL.subselect.session"); mcrSession.deleteObject("XSL.subselect.varpath"); mcrSession.deleteObject("XSL.subselect.webpage"); sendToSubSelect(job.getResponse(), parms, variables, root); } private void processSubmit(MCRServletJob job, MCRRequestParameters parms) throws ServletException, IOException, TransformerException, SAXException { LOGGER.debug("Editor: process submit"); String sessionID = parms.getParameter("_session"); MCREditorSession editorSession = MCREditorSessionCache.instance().getEditorSession(sessionID); if (editorSession == null) { LOGGER.error("No editor for session <" + sessionID + ">"); throw new ServletException("invalid session"); } Element editor = editorSession.getXML(); String button = null; for (Enumeration e = parms.getParameterNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); if (name.startsWith("_p-") || name.startsWith("_m-") || name.startsWith("_u-") || name.startsWith("_d-") || name.startsWith("_s-")) { button = name; break; } } if (button == null) { LOGGER.info("Editor session " + sessionID + " submitting form data"); // We do not remove the editor session from cache, because // the user may use the back button and re-submit if an error // occured. In nearly all editor states this is safe, but sometimes // may result in ugly behaviour in conjunction with repeaters. // // sessions.remove(sessionID); // no good idea processTargetSubmission(job, parms, editor); } else if (button.startsWith("_s-")) { StringTokenizer sst = new StringTokenizer(button.substring(3), "-"); String id = sst.nextToken(); String var = sst.nextToken(); LOGGER.debug("Editor start subselect " + id + " at position " + var); Element subselect = MCREditorDefReader.findElementByID(id, editor); StringBuilder sb = new StringBuilder(MCRFrontendUtil.getBaseURL()); String webpage = URLEncoder.encode(parms.getParameter("_webpage"), "UTF-8"); if ("editor".equals(subselect.getAttributeValue("type"))) { sb.append(subselect.getAttributeValue("href")); sb.append("?subselect.session=").append(sessionID); sb.append("&subselect.varpath=").append(var); sb.append("&subselect.webpage=").append(webpage); String wp = MCRFrontendUtil.getBaseURL() + parms.getParameter("_webpage"); if (!wp.contains("XSL.editor.session.id")) { wp += "XSL.editor.session.id=" + sessionID; } String cancelURL = URLEncoder.encode(wp, "UTF-8"); sb.append("&cancelURL=").append(cancelURL); } else if ("webpage".equals(subselect.getAttributeValue("type"))) { sb.append(subselect.getAttributeValue("href")); if (subselect.getAttributeValue("href").indexOf("?") > 0) { sb.append("&XSL.subselect.session.SESSION=").append(sessionID); } else { sb.append("?XSL.subselect.session.SESSION=").append(sessionID); } sb.append("&XSL.subselect.varpath.SESSION=").append(var); sb.append("&XSL.subselect.webpage.SESSION=").append(webpage); } else if ("servlet".equals(subselect.getAttributeValue("type"))) { sb.append(subselect.getAttributeValue("href")); sb.append("?subselect.session=").append(sessionID); sb.append("&subselect.varpath=").append(var); sb.append("&subselect.webpage=").append(webpage); } String url = sb.toString(); editor.removeChild("input"); editor.removeChild("repeats"); MCREditorSubmission sub = new MCREditorSubmission(parms, editor, false); editor.addContent(sub.buildInputElements()); editor.addContent(sub.buildRepeatElements()); LOGGER.debug("Editor goto subselect at " + url); job.getResponse().sendRedirect(job.getResponse().encodeRedirectURL(url)); } else { int pos = button.lastIndexOf("-"); String action = button.substring(1, 2); String path = button.substring(3, pos); int nr = Integer.parseInt(button.substring(pos + 1, button.length() - 2)); LOGGER.debug("Editor action " + action + " " + nr + " " + path); editor.removeChild("input"); editor.removeChild("repeats"); editor.removeChild("failed"); MCREditorSubmission sub = new MCREditorSubmission(parms, editor, false); if ("p".equals(action)) { sub.doPlus(path, nr); } else if ("m".equals(action)) { sub.doMinus(path, nr); } else if ("u".equals(action)) { sub.doUp(path, nr); } else if ("d".equals(action)) { sub.doUp(path, nr + 1); } editor.addContent(sub.buildInputElements()); editor.addContent(sub.buildRepeatElements()); // Redirect to webpage to reload editor form StringBuilder sb = new StringBuilder(MCRFrontendUtil.getBaseURL()); String wp = parms.getParameter("_webpage"); sb.append(wp); if (!wp.contains("XSL.editor.session.id=")) { sb.append("XSL.editor.session.id="); sb.append(sessionID); } path = path.replace('/', '_').replace('@', '_').replace('[', '_').replace(']', '_'); sb.append("#rep").append(path); LOGGER.debug("Editor redirect to " + sb.toString()); job.getResponse().sendRedirect(job.getResponse().encodeRedirectURL(sb.toString())); } } private void processTargetSubmission(MCRServletJob job, MCRRequestParameters parms, Element editor) throws ServletException, IOException, TransformerException, SAXException { LOGGER.debug("Editor: processTargetSubmission "); HttpServletRequest req = job.getRequest(); HttpServletResponse res = job.getResponse(); editor.removeChild("failed"); MCREditorSubmission sub = new MCREditorSubmission(parms, editor, true); if (sub.errors()) // validation failed, go back to editor form in // webpage { editor.removeChild("input"); editor.removeChild("repeats"); editor.addContent(sub.buildInputElements()); editor.addContent(sub.buildRepeatElements()); editor.addContent(sub.buildFailedConditions()); String sessionID = parms.getParameter("_session"); // Redirect to webpage to reload editor form StringBuilder sb = new StringBuilder(MCRFrontendUtil.getBaseURL()); String wp = parms.getParameter("_webpage"); sb.append(wp); if (!wp.contains("XSL.editor.session.id=")) { sb.append("XSL.editor.session.id="); sb.append(sessionID); } LOGGER.debug("Editor redirect to " + sb.toString()); res.sendRedirect(res.encodeRedirectURL(sb.toString())); return; } Document xml = sub.getXML(); postProcess(editor, sub); String targetType = parms.getParameter("_target-type"); LOGGER.debug("Editor: targettype=" + targetType); if (targetType.equals("servlet")) { sendToServlet(req, res, sub); } else if (targetType.equals("url")) { sendToURL(req, res); } else if (targetType.equals("webapp")) { sendToWebAppFile(req, res, sub, editor); } else if (targetType.equals("debug")) { sendToDebug(res, xml, sub); } else if (targetType.equals("display")) { getLayoutService().doLayout(req, res, new MCRJDOMContent(sub.getXML())); } else if (targetType.equals("subselect")) { List variables = sub.getVariables(); String root = sub.getXML().getRootElement().getName(); sendToSubSelect(res, parms, variables, root); } else { LOGGER.debug("Unknown targettype"); } LOGGER.debug("Editor: processTargetSubmission DONE"); } private void postProcess(Element editor, MCREditorSubmission sub) { Element postProcessorConfiguration = editor.getChild("postprocessor"); if (postProcessorConfiguration != null) { String clazz = postProcessorConfiguration.getAttributeValue("class"); LOGGER.debug("Transforming editor submission with " + clazz); try { Object instance = Class.forName(clazz).newInstance(); MCREditorPostProcessor postprocessor = (MCREditorPostProcessor) instance; postprocessor.init(postProcessorConfiguration); Document input = sub.getXML(); input = postprocessor.process(input); sub.setXML(input); } catch (Exception ex) { String msg = "Exception when postprocessing input with " + clazz; throw new MCRException(msg, ex); } } } private void sendToServlet(HttpServletRequest req, HttpServletResponse res, MCREditorSubmission sub) throws IOException, ServletException { String name = sub.getParameters().getParameter("_target-name"); String url = sub.getParameters().getParameter("_target-url"); LOGGER.debug("name=" + name + " url=" + url); RequestDispatcher rd = null; if (name != null && name.trim().length() > 0) { rd = getServletContext().getNamedDispatcher(name); } else if (url != null && url.trim().length() > 0) { rd = getServletContext().getRequestDispatcher(url); } LOGGER.debug("rd=" + rd); if (rd != null) { req.setAttribute("MCREditorSubmission", sub); rd.forward(req, res); } } private void sendToURL(HttpServletRequest req, HttpServletResponse res) throws IOException { StringBuilder url = new StringBuilder(req.getParameter("_target-url")); url.append('?').append(req.getQueryString()); res.sendRedirect(res.encodeRedirectURL(url.toString())); } /** * Writes the output of editor to a local file in the web application * directory. Usage: <target type="webapp" name="{relative_path_to_file}" * url="{redirect url after file is written}" /> If cancel url is given, * user is redirected to that page, otherwise target url is used. * * @throws IOException */ private void sendToWebAppFile(HttpServletRequest req, HttpServletResponse res, MCREditorSubmission sub, Element editor) throws IOException { String path = sub.getParameters().getParameter("_target-name"); LOGGER.debug("Writing editor output to webapp file " + path); File f = getWebAppFile(path); if (!f.getParentFile().exists()) { f.getParentFile().mkdirs(); } FileOutputStream fout = new FileOutputStream(f); XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat().setEncoding("UTF-8")); outputter.output(sub.getXML(), fout); fout.close(); String url = sub.getParameters().getParameter("_target-url"); Element cancel = editor.getChild("cancel"); if ((url == null || url.length() == 0) && cancel != null && cancel.getAttributeValue("url") != null) { url = cancel.getAttributeValue("url"); } LOGGER.debug("EditorServlet redirecting to " + url); res.sendRedirect(res.encodeRedirectURL(url)); } private File getWebAppFile(String path) throws FileNotFoundException { String realPath = getServletContext().getRealPath(path); if (realPath != null) { return new File(realPath); } //resource is resolvable but not mappable (inside JAR?) String contextPath = getServletContext().getRealPath("/"); if (contextPath == null) { //WAR is not unpacked throw new FileNotFoundException("Cannot map " + path + " to local file"); } File baseDir = new File(contextPath); return new File(baseDir, path); } private void sendToSubSelect(HttpServletResponse res, MCRRequestParameters parms, List variables, String root) throws IOException { String webpage = parms.getParameter("subselect.webpage"); String sessionID = parms.getParameter("subselect.session"); Element editor = MCREditorSessionCache.instance().getEditorSession(sessionID).getXML(); MCREditorSubmission subnew = new MCREditorSubmission(editor, variables, root, parms); editor.removeChild("input"); editor.removeChild("repeats"); editor.removeChild("failed"); editor.addContent(subnew.buildInputElements()); editor.addContent(subnew.buildRepeatElements()); // Redirect to webpage to reload editor form StringBuilder sb = new StringBuilder(MCRFrontendUtil.getBaseURL()); sb.append(webpage); if (!webpage.contains("XSL.editor.session.id=")) { sb.append("XSL.editor.session.id="); sb.append(sessionID); } LOGGER.debug("Editor redirect to " + sb.toString()); res.sendRedirect(res.encodeRedirectURL(sb.toString())); } private void sendToDebug(HttpServletResponse res, Document unprocessed, MCREditorSubmission sub) throws IOException, UnsupportedEncodingException { res.setContentType("text/html; charset=UTF-8"); PrintWriter pw = res.getWriter(); pw.println("<html><body><p><pre>"); for (int i = 0; i < sub.getVariables().size(); i++) { MCREditorVariable var = (MCREditorVariable) sub.getVariables().get(i); pw.println(var.getPath() + " = " + var.getValue()); FileItem file = var.getFile(); if (file != null) { pw.println(" is uploaded file " + file.getContentType() + ", " + file.getSize() + " bytes"); } } pw.println("</pre></p><p>"); XMLOutputter outputter = new XMLOutputter(); Format fmt = Format.getPrettyFormat(); fmt.setLineSeparator("\n"); fmt.setOmitDeclaration(true); outputter.setFormat(fmt); Element pre = new Element("pre"); pre.addContent(outputter.outputString(unprocessed)); outputter.output(pre, pw); pre = new Element("pre"); pre.addContent(outputter.outputString(sub.getXML())); outputter.output(pre, pw); pw.println("</p></body></html>"); pw.close(); } }