
de.escalon.hypermedia.spring.xhtml.XhtmlWriter Maven / Gradle / Ivy
The newest version!
package de.escalon.hypermedia.spring.xhtml;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.escalon.hypermedia.PropertyUtils;
import de.escalon.hypermedia.action.Input;
import de.escalon.hypermedia.action.Type;
import de.escalon.hypermedia.affordance.ActionDescriptor;
import de.escalon.hypermedia.affordance.ActionInputParameter;
import de.escalon.hypermedia.affordance.Affordance;
import de.escalon.hypermedia.affordance.DataType;
import de.escalon.hypermedia.spring.DefaultDocumentationProvider;
import de.escalon.hypermedia.spring.DocumentationProvider;
import de.escalon.hypermedia.spring.SpringActionInputParameter;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.Property;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.*;
import static de.escalon.hypermedia.spring.xhtml.XhtmlWriter.OptionalAttributes.attr;
/**
* Created by Dietrich on 09.02.2015.
*/
public class XhtmlWriter extends Writer {
private Writer writer;
private List stylesheets = Collections.emptyList();
public static final String HTML_HEAD_START = "" + //
//"" + // formatter
"" + //
"" + //
" " + //
" " + //
" %s ";
public static final String HTML_STYLESHEET = "" + //
" ";
public static final String HTML_HEAD_END = "" + //
" " + //
" " + //
" \n" + //
" ";
public static final String HTML_END = "" + //
" " +
" " +
" " + //
"";
private String methodParam = "_method";
private DocumentationProvider documentationProvider = new DefaultDocumentationProvider();
private String formControlClass = "form-control";
private String formGroupClass = "form-group";
private String controlLabelClass = "control-label";
public XhtmlWriter(Writer writer) {
this.writer = writer;
}
public void setMethodParam(String methodParam) {
this.methodParam = methodParam;
}
public void beginHtml(String title) throws IOException {
write(String.format(HTML_HEAD_START, title));
for (String stylesheet : stylesheets) {
write(String.format(HTML_STYLESHEET, stylesheet));
}
write(String.format(HTML_HEAD_END, title));
}
public void endHtml() throws IOException {
write(HTML_END);
}
public void beginDiv() throws IOException {
writer.write("");
}
public void beginDiv(OptionalAttributes attributes) throws IOException {
writer.write("");
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
writer.write(cbuf, off, len);
}
@Override
public void flush() throws IOException {
writer.flush();
}
@Override
public void close() throws IOException {
writer.close();
}
public void beginUnorderedList() throws IOException {
writer.write("");
}
public void endUnorderedList() throws IOException {
writer.write("
");
}
public void beginListItem() throws IOException {
writer.write("");
}
public void endListItem() throws IOException {
writer.write(" ");
}
public void beginSpan() throws IOException {
writer.write("");
}
public void endSpan() throws IOException {
writer.write("");
}
public void beginDl() throws IOException {
// TODO: make this configurable?
writer.write("");
}
public void endDl() throws IOException {
writer.write("
");
}
public void beginDt() throws IOException {
writer.write("");
}
public void endDt() throws IOException {
writer.write(" ");
}
public void beginDd() throws IOException {
writer.write("");
}
public void endDd() throws IOException {
writer.write(" ");
}
public void writeSpan(Object value) throws IOException {
beginSpan();
writer.write(value.toString());
endSpan();
}
public void writeDefinitionTerm(Object value) throws IOException {
beginDt();
writer.write(value.toString());
endDt();
}
public void setStylesheets(List stylesheets) {
Assert.notNull(stylesheets);
this.stylesheets = stylesheets;
}
public void setDocumentationProvider(DocumentationProvider documentationProvider) {
this.documentationProvider = documentationProvider;
}
public static class OptionalAttributes {
private Map attributes = new LinkedHashMap();
@Override
public String toString() {
return attributes.toString();
}
/**
* Creates OptionalAttributes with one optional attribute having name if value is not null.
*
* @param name
* of first attribute
* @param value
* may be null
* @return builder with one attribute, attr builder if value is null
*/
public static OptionalAttributes attr(String name, String value) {
Assert.isTrue(name != null && value != null || value == null);
OptionalAttributes attributeBuilder = new OptionalAttributes();
addAttributeIfValueNotNull(name, value, attributeBuilder);
return attributeBuilder;
}
private static void addAttributeIfValueNotNull(String name, String value, OptionalAttributes attributeBuilder) {
if (value != null) {
attributeBuilder.attributes.put(name, value);
}
}
public OptionalAttributes and(String name, String value) {
addAttributeIfValueNotNull(name, value, this);
return this;
}
public Map build() {
return attributes;
}
/**
* Creates OptionalAttributes builder.
*
* @return builder
*/
public static OptionalAttributes attr() {
return attr(null, null);
}
}
public void writeLinks(List links) throws IOException {
for (Link link : links) {
if (link instanceof Affordance) {
Affordance affordance = (Affordance) link;
List actionDescriptors = affordance.getActionDescriptors();
if (actionDescriptors.isEmpty()) {
// treat like simple link
appendLinkWithoutActionDescriptor(affordance);
} else {
if (affordance.isTemplated()) {
// TODO ensure that template expansion always takes place for base uri
if (!affordance.isBaseUriTemplated()) {
for (ActionDescriptor actionDescriptor : actionDescriptors) {
RequestMethod httpMethod = RequestMethod.valueOf(actionDescriptor.getHttpMethod());
// html does not allow templated action attr for forms, only render GET form
if (RequestMethod.GET == httpMethod) {
// TODO: partial uritemplate query must become hidden field
appendForm(affordance, actionDescriptor);
}
// TODO write human-readable description of additional methods?
}
}
} else {
for (ActionDescriptor actionDescriptor : actionDescriptors) {
// TODO write documentation about the supported action and maybe fields?
if ("GET".equals(actionDescriptor.getHttpMethod()) &&
actionDescriptor.getRequestParamNames()
.isEmpty()) {
beginDiv();
// GET without params is simple
writeAnchor(OptionalAttributes.attr("href", affordance.expand()
.getHref())
.and("rel", affordance.getRel()), affordance.getRel());
endDiv();
} else {
appendForm(affordance, actionDescriptor);
}
}
}
}
} else { // simple link, may be templated
appendLinkWithoutActionDescriptor(link);
}
}
}
/**
* Appends form and squashes non-GET or POST to POST. If required, adds _method field for handling by an
* appropriate
* filter such as Spring's HiddenHttpMethodFilter.
*
* @param affordance
* to make into a form
* @param actionDescriptor
* describing the form action
* @throws IOException
* @see
*
* Spring
* MVC HiddenHttpMethodFilter
*/
private void appendForm(Affordance affordance, ActionDescriptor actionDescriptor) throws IOException {
String formName = actionDescriptor.getActionName();
RequestMethod httpMethod = RequestMethod.valueOf(actionDescriptor.getHttpMethod());
// Link's expand method removes non-required variables from URL
String actionUrl = affordance.expand()
.getHref();
beginForm(OptionalAttributes.attr("action", actionUrl)
.and("method", getHtmlConformingHttpMethod(httpMethod))
.and("name", formName));
write("");
write("Form " + formName);
write("
");
writeHiddenHttpMethodField(httpMethod);
// build the form
if (actionDescriptor.hasRequestBody()) { // parameter bean
ActionInputParameter requestBody = actionDescriptor.getRequestBody();
Class> parameterType = requestBody.getParameterType();
recurseBeanProperties(parameterType, actionDescriptor, requestBody, requestBody.getValue(), "");
} else { // plain parameter list
Collection requestParams = actionDescriptor.getRequestParamNames();
for (String requestParamName : requestParams) {
ActionInputParameter actionInputParameter = actionDescriptor.getActionInputParameter(requestParamName);
Object[] possibleValues = actionInputParameter.getPossibleValues(actionDescriptor);
// TODO duplication with appendInputOrSelect
if (possibleValues.length > 0) {
if (actionInputParameter.isArrayOrCollection()) {
appendSelectMulti(requestParamName, possibleValues, actionInputParameter);
} else {
appendSelectOne(requestParamName, possibleValues, actionInputParameter);
}
} else {
if (actionInputParameter.isArrayOrCollection()) {
// have as many inputs as there are call values, list of 5 nulls gives you five input fields
// TODO support for free list input instead, code on demand?
Object[] callValues = actionInputParameter.getValues();
int items = callValues.length;
for (int i = 0; i < items; i++) {
Object value;
if (i < callValues.length) {
value = callValues[i];
} else {
value = null;
}
appendInput(requestParamName, actionInputParameter, value, actionInputParameter
.isReadOnly(requestParamName)); // not readonly
}
} else {
String callValueFormatted = actionInputParameter.getValueFormatted();
appendInput(requestParamName, actionInputParameter, callValueFormatted, actionInputParameter
.isReadOnly(requestParamName)); // not readonly
}
}
}
}
inputButton(Type.SUBMIT, capitalize(httpMethod.name()
.toLowerCase()));
endForm();
}
private void appendLinkWithoutActionDescriptor(Link link) throws IOException {
if (link.isTemplated()) {
// TODO ensure that template expansion takes place for base uri
Link expanded = link.expand(); // remove query variables
beginForm(OptionalAttributes.attr("action", expanded.getHref())
.and("method", "GET"));
List variables = link.getVariables();
for (TemplateVariable variable : variables) {
String variableName = variable.getName();
String label = variable.hasDescription() ? variable.getDescription() : variableName;
writeLabelWithDoc(label, variableName, null); // no documentation url
input(variableName, Type.TEXT);
}
} else {
String rel = link.getRel();
String title = (rel != null ? rel : link.getHref());
// TODO: write html instead of anchor here?
writeAnchor(OptionalAttributes.attr("href", link.getHref())
.and("rel", link.getRel()), title);
}
}
/**
* Classic submit or reset button.
*
* @param type
* submit or reset
* @param value
* caption on the button
* @throws IOException
*/
private void inputButton(Type type, String value) throws IOException {
write("");
}
private void input(String fieldName, Type type, OptionalAttributes attributes) throws IOException {
write("");
}
private void input(String fieldName, Type type) throws IOException {
input(fieldName, type, OptionalAttributes.attr());
}
// private void beginLabel(String label) throws IOException {
// beginLabel(label, attr());
// }
// private void beginLabel(String label, OptionalAttributes attributes) throws IOException {
// beginLabel(attributes);
// write(label);
// }
private void beginLabel(OptionalAttributes attributes) throws IOException {
write("
© 2015 - 2025 Weber Informatics LLC | Privacy Policy