package com.revolsys.ui.web.rest.converter; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageNotWritableException; import com.revolsys.collection.map.MapEx; import com.revolsys.collection.map.NamedLinkedHashMapEx; import com.revolsys.datatype.DataTypes; import com.revolsys.io.FileUtil; import com.revolsys.io.IoConstants; import com.revolsys.io.IoFactory; import com.revolsys.io.map.MapWriter; import com.revolsys.io.map.MapWriterFactory; import com.revolsys.record.io.format.xml.XmlConstants; import com.revolsys.record.io.format.xml.XmlWriter; import com.revolsys.record.io.format.xml.wadl.WadlConstants; import com.revolsys.ui.html.view.Element; import com.revolsys.ui.model.DocInfo; import com.revolsys.ui.model.PageInfo; import com.revolsys.ui.model.ParameterInfo; import com.revolsys.ui.web.utils.HttpServletUtils; import com.revolsys.util.CaseConverter; import com.revolsys.util.HtmlAttr; import com.revolsys.util.HtmlElem; import com.revolsys.util.HtmlUtil; import com.revolsys.util.Property; public class PageInfoHttpMessageConverter extends AbstractHttpMessageConverter<PageInfo> implements WadlConstants { private static final MediaType APPLICATION_VND_SUN_WADL_XML = MediaType .parseMediaType("application/vnd.sun.wadl+xml"); private static Map<MediaType, String> MEDIA_TYPE_TO_EXTENSION_MAP = new HashMap<>(); private static final MediaType TEXT_URI_LIST = MediaType.parseMediaType("text/uri-list"); private static final Collection<MediaType> WRITE_MEDIA_TYPES = Arrays.asList( MediaType.APPLICATION_XHTML_XML, MediaType.TEXT_HTML, APPLICATION_VND_SUN_WADL_XML, TEXT_URI_LIST, MediaType.APPLICATION_JSON, MediaType.TEXT_XML); static { MEDIA_TYPE_TO_EXTENSION_MAP.put(MediaType.APPLICATION_XHTML_XML, ".html"); MEDIA_TYPE_TO_EXTENSION_MAP.put(MediaType.TEXT_HTML, ".html"); MEDIA_TYPE_TO_EXTENSION_MAP.put(APPLICATION_VND_SUN_WADL_XML, ".wadl"); MEDIA_TYPE_TO_EXTENSION_MAP.put(TEXT_URI_LIST, ".uri-list"); MEDIA_TYPE_TO_EXTENSION_MAP.put(MediaType.APPLICATION_JSON, ".json"); MEDIA_TYPE_TO_EXTENSION_MAP.put(MediaType.TEXT_XML, ".xml"); } public PageInfoHttpMessageConverter() { super(PageInfo.class, Collections.emptySet(), WRITE_MEDIA_TYPES); } private Map<String, Object> getMap(final String url, final PageInfo pageInfo) { final MapEx pageMap = new NamedLinkedHashMapEx("resource"); pageMap.put("resourceUri", url); pageMap.put("title", pageInfo.getTitle()); final String description = pageInfo.getDescription(); if (Property.hasValue(description)) { pageMap.put("description", description); } for (final Entry<String, Object> attribute : pageInfo.getFields().entrySet()) { final String key = attribute.getKey(); final Object value = attribute.getValue(); pageMap.put(key, value); } final List<Map<String, Object>> childPages = new ArrayList<>(); for (final Entry<String, PageInfo> childPage : pageInfo.getPages().entrySet()) { final String childPath = childPage.getKey(); final PageInfo childPageInfo = childPage.getValue(); final String childUri = getUrl(url, childPath); final Map<String, Object> childPageMap = getMap(childUri, childPageInfo); childPages.add(childPageMap); } if (!childPages.isEmpty()) { pageMap.put("resources", childPages); } return pageMap; } private String getUrl(final String parentUrl, final String childUrl) { String childUri; if (childUrl.startsWith("http")) { return childUrl; } else if (childUrl.startsWith("/")) { childUri = HttpServletUtils.getServerUrl() + childUrl; } else if (parentUrl.charAt(parentUrl.length() - 1) != '/') { childUri = parentUrl + "/" + childUrl; } else { childUri = parentUrl + childUrl; } return childUri; } @Override public void write(final PageInfo pageInfo, final MediaType mediaType, final HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { if (!HttpServletUtils.getResponse().isCommitted()) { if (pageInfo != null) { final Charset charset = HttpServletUtils.setContentTypeWithCharset(outputMessage, mediaType); final HttpServletRequest request = HttpServletUtils.getRequest(); String url = pageInfo.getUrl(); if (url == null) { url = HttpServletUtils.getFullRequestUrl(); final String extension = MEDIA_TYPE_TO_EXTENSION_MAP.get(mediaType); if (extension != null) { url = url.replaceAll(extension + "/?$", "/"); } } else if (url.startsWith("/")) { url = HttpServletUtils.getServerUrl() + url; } final boolean showTitle = !"false".equalsIgnoreCase(request.getParameter("showTitle")); final OutputStream out = outputMessage.getBody(); if (APPLICATION_VND_SUN_WADL_XML.equals(mediaType)) { writeWadl(out, url, pageInfo); } else if (MediaType.TEXT_HTML.equals(mediaType) || MediaType.APPLICATION_XHTML_XML.equals(mediaType)) { writeHtml(out, url, pageInfo, showTitle); } else if (TEXT_URI_LIST.equals(mediaType)) { writeUriList(out, url, pageInfo); } else { writeResourceList(mediaType, charset, out, url, pageInfo); } } } } @SuppressWarnings("unchecked") private void writeHtml(final OutputStream out, final String url, final PageInfo pageInfo, final boolean showTitle) { final XmlWriter writer = new XmlWriter(out); writer.startTag(HtmlElem.DIV); if (showTitle) { writer.element(HtmlElem.H1, pageInfo.getTitle()); final DocInfo docInfo = pageInfo.getDefaultDocumentation(); if (docInfo != null) { writer.startTag(HtmlElem.DIV); writer.attribute(HtmlAttr.STYLE, "margin-bottom: 1em"); final String description = docInfo.getDescription(); if (description != null) { if (docInfo.isHtml()) { writer.write(description); } else { writer.element(HtmlElem.P, description); } } writer.endTag(HtmlElem.DIV); } } final HttpServletRequest request = HttpServletUtils.getRequest(); for (final String method : pageInfo.getMethods()) { @SuppressWarnings("rawtypes") final Map parameterMap = request.getParameterMap(); writeMethod(writer, url, pageInfo, method, parameterMap); } final Map<String, PageInfo> pages = pageInfo.getPages(); final Element pagesElement = pageInfo.getPagesElement(); if (pagesElement != null) { pagesElement.serialize(writer); } else if (!pages.isEmpty()) { writer.startTag(HtmlElem.DIV); writer.attribute(HtmlAttr.CLASS, "resources"); writer.startTag(HtmlElem.DL); for (final Entry<String, PageInfo> childPage : pages.entrySet()) { final String childPath = childPage.getKey(); final PageInfo childPageInfo = childPage.getValue(); String childUri; if (childPath.startsWith("/") || childPath.startsWith("http")) { childUri = childPath; } else if (url.charAt(url.length() - 1) != '/') { childUri = url + "/" + childPath; } else { childUri = url + childPath; } writer.startTag(HtmlElem.DT); final String childTitle = childPageInfo.getTitle(); HtmlUtil.serializeA(writer, null, childUri, childTitle); writer.endTag(HtmlElem.DT); final boolean isTemplate = childPath.matches(".*(\\{[^\\}]+\\}.*)+"); final String childDescription = childPageInfo.getDescription(); if (childDescription != null || isTemplate) { writer.startTag(HtmlElem.DD); if (childDescription != null) { writer.element(HtmlElem.P, childDescription); } if (isTemplate) { writer.startTag(HtmlElem.FORM); writer.attribute(HtmlAttr.ACTION, childUri); writer.attribute(HtmlAttr.METHOD, "get"); for (final String pathElement : childPath.split("/")) { if (pathElement.matches("\\{[^\\}]+\\}")) { final String name = pathElement.substring(1, pathElement.length() - 1); HtmlUtil.serializeTextInput(writer, name, name, 20, 255); } } HtmlUtil.serializeButtonInput(writer, "go", "doGet(this.form)"); writer.endTag(HtmlElem.FORM); } writer.endTag(HtmlElem.DD); } } writer.endTag(HtmlElem.DL); writer.endTag(HtmlElem.DIV); } writer.endTag(HtmlElem.DIV); writer.endDocument(); writer.close(); } private void writeHtmlField(final XmlWriter writer, final ParameterInfo parameter, final Map<String, ?> formValues) { final String parameterName = parameter.getName(); final Object defaultValue = parameter.getDefaultValue(); Object value = formValues.get(parameterName); if (value == null) { value = defaultValue; } writer.startTag(HtmlElem.TR); writer.startTag(HtmlElem.TH); writer.startTag(HtmlElem.LABEL); writer.attribute(HtmlAttr.FOR, parameterName); writer.text(CaseConverter.toCapitalizedWords(parameterName)); writer.endTag(HtmlElem.LABEL); writer.endTag(HtmlElem.TH); writer.startTag(HtmlElem.TD); final int maxLength = 255; HtmlUtil.serializeTextInput(writer, parameterName, value, 50, maxLength); writeInstructions(writer, parameter); writer.endTag(HtmlElem.TD); writer.endTag(HtmlElem.TR); } private void writeHtmlFileField(final XmlWriter writer, final ParameterInfo parameter, final Map<String, ?> formValues) { final String name = parameter.getName(); final Object value = formValues.get(name); writer.startTag(HtmlElem.TR); writer.startTag(HtmlElem.TH); writer.startTag(HtmlElem.LABEL); writer.attribute(HtmlAttr.FOR, name); writer.text(CaseConverter.toCapitalizedWords(name)); writer.endTag(HtmlElem.LABEL); writer.endTag(HtmlElem.TH); writer.startTag(HtmlElem.TD); HtmlUtil.serializeFileInput(writer, name, value); writeInstructions(writer, parameter); writer.endTag(HtmlElem.TD); writer.endTag(HtmlElem.TR); } private void writeHtmlSelect(final XmlWriter writer, final ParameterInfo parameter, final Map<String, ?> formValues, final List<? extends Object> values) { final String name = parameter.getName(); final boolean optional = !parameter.isRequired(); Object value = formValues.get(name); if (value == null) { value = parameter.getDefaultValue(); } writer.startTag(HtmlElem.TR); writer.startTag(HtmlElem.TH); writer.startTag(HtmlElem.LABEL); writer.attribute(HtmlAttr.FOR, name); writer.text(CaseConverter.toCapitalizedWords(name)); writer.endTag(HtmlElem.LABEL); writer.endTag(HtmlElem.TH); writer.startTag(HtmlElem.TD); HtmlUtil.serializeSelect(writer, name, value, optional, values); writeInstructions(writer, parameter); writer.endTag(HtmlElem.TD); writer.endTag(HtmlElem.TR); } private void writeHtmlSelect(final XmlWriter writer, final ParameterInfo parameter, final Map<String, ?> formValues, final Map<? extends Object, ? extends Object> values) { final String name = parameter.getName(); final boolean optional = !parameter.isRequired(); Object value = formValues.get(name); if (value == null) { value = parameter.getDefaultValue(); } writer.startTag(HtmlElem.TR); writer.startTag(HtmlElem.TH); writer.startTag(HtmlElem.LABEL); writer.attribute(HtmlAttr.FOR, name); writer.text(CaseConverter.toCapitalizedWords(name)); writer.endTag(HtmlElem.LABEL); writer.endTag(HtmlElem.TH); writer.startTag(HtmlElem.TD); HtmlUtil.serializeSelect(writer, name, value, optional, values); writeInstructions(writer, parameter); writer.endTag(HtmlElem.TD); writer.endTag(HtmlElem.TR); } private void writeInstructions(final XmlWriter writer, final ParameterInfo parameter) { final String description = parameter.getDescription(); if (Property.hasValue(description)) { writer.startTag(HtmlElem.DIV); writer.attribute(HtmlAttr.CLASS, "fieldDescription"); writer.text(description); writer.endTag(HtmlElem.DIV); } } private void writeMethod(final XmlWriter writer, final String url, final PageInfo pageInfo, final String method, final Map<String, Object> values) { final Collection<ParameterInfo> parameters = pageInfo.getParameters(); final boolean hasParameters = !parameters.isEmpty(); if (hasParameters) { // final String title = method; // writer.element(HtmlUtil.H2, title); if (hasParameters) { writer.startTag(HtmlElem.DIV); writer.attribute(HtmlAttr.CLASS, "form"); writer.startTag(HtmlElem.FORM); writer.attribute(HtmlAttr.ACTION, url); writer.attribute(HtmlAttr.TARGET, "_top"); if (method.equals("post")) { boolean isFormData = false; for (final MediaType mediaType : pageInfo.getInputContentTypes()) { if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType)) { isFormData = true; } } if (isFormData) { writer.attribute(HtmlAttr.ENCTYPE, MediaType.MULTIPART_FORM_DATA.toString()); } } writer.attribute(HtmlAttr.METHOD, method); writer.startTag(HtmlElem.DIV); writer.attribute(HtmlAttr.CLASS, "objectView"); writer.startTag(HtmlElem.TABLE); writer.attribute(HtmlAttr.CLASS, "data"); writer.startTag(HtmlElem.TBODY); for (final ParameterInfo parameter : parameters) { final Map<Object, Object> options = parameter.getAllowedValues(); if (options.isEmpty()) { if (parameter.getType().equals(DataTypes.BASE64_BINARY)) { writeHtmlFileField(writer, parameter, values); } else { writeHtmlField(writer, parameter, values); } } else { writeHtmlSelect(writer, parameter, values, options); } } } final List<MediaType> mediaTypes = pageInfo.getMediaTypes(); if (!mediaTypes.isEmpty()) { final ParameterInfo parameter = new ParameterInfo("format", true, DataTypes.STRING, "Select the file format to return the result data in.", mediaTypes); writeHtmlSelect(writer, parameter, values, mediaTypes); } writer.endTag(HtmlElem.TBODY); writer.endTag(HtmlElem.TABLE); writer.endTag(HtmlElem.DIV); writer.startTag(HtmlElem.DIV); writer.attribute(HtmlAttr.CLASS, "actionMenu"); HtmlUtil.serializeSubmitInput(writer, "Submit", "submit"); writer.endTag(HtmlElem.DIV); writer.endTag(HtmlElem.FORM); writer.endTag(HtmlElem.DIV); } } public void writeResourceList(final MediaType mediaType, Charset charset, final OutputStream out, final String url, final PageInfo pageInfo) { if (charset == null) { charset = StandardCharsets.UTF_8; } final String mediaTypeString = mediaType.getType() + "/" + mediaType.getSubtype(); final MapWriterFactory writerFactory = IoFactory.factoryByMediaType(MapWriterFactory.class, mediaTypeString); if (writerFactory != null) { final MapWriter writer = writerFactory.newMapWriter(out, charset); writer.setProperty(IoConstants.INDENT, true); writer.setProperty(IoConstants.SINGLE_OBJECT_PROPERTY, true); final HttpServletRequest request = HttpServletUtils.getRequest(); String callback = request.getParameter("jsonp"); if (callback == null) { callback = request.getParameter("callback"); } if (callback != null) { writer.setProperty(IoConstants.JSONP_PROPERTY, callback); } final Map<String, Object> page = getMap(url, pageInfo); writer.write(page); writer.close(); } } private void writeUriList(final OutputStream out, final String url, final PageInfo pageInfo) throws IOException { final Writer writer = FileUtil.newUtf8Writer(out); try { for (final String childPath : pageInfo.getPages().keySet()) { String childUri; if (childPath.startsWith("/")) { childUri = childPath; } else { childUri = url + childPath; } writer.write(url + childUri); writer.write("\r\n"); } } finally { FileUtil.closeSilent(writer); } } private void writeWadl(final OutputStream out, final String url, final PageInfo pageInfo) { final XmlWriter writer = new XmlWriter(out); writer.startDocument("UTF-8", "1.0"); writer.startTag(APPLICATION); writer.startTag(RESOURCES); writeWadlResource(writer, url, pageInfo, true); writer.endTag(RESOURCES); writer.endTag(APPLICATION); writer.endDocument(); writer.close(); } private void writeWadlDoc(final XmlWriter writer, final PageInfo pageInfo) { for (final DocInfo documentation : pageInfo.getDocumentation()) { final String title = documentation.getTitle(); final String description = documentation.getDescription(); if (title != null && description != null) { writer.startTag(DOC); final Locale locale = documentation.getLocale(); if (locale != null) { writer.attribute(XmlConstants.XML_LANG, locale); } writer.attribute(TITLE, title); writer.text(description); writer.endTag(DOC); } } } private void writeWadlResource(final XmlWriter writer, final String url, final PageInfo pageInfo, final boolean writeChildren) { writer.startTag(RESOURCE); writer.attribute(PATH, url); writeWadlDoc(writer, pageInfo); if (writeChildren) { for (final Entry<String, PageInfo> childPage : pageInfo.getPages().entrySet()) { final String childPath = childPage.getKey(); final PageInfo childPageInfo = childPage.getValue(); writeWadlResource(writer, childPath, childPageInfo, false); } } writer.endTag(RESOURCE); } }