/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* Copyright (c) 2013, MPL CodeInside http://codeinside.ru
*/
package ru.codeinside.gses.webui.form;
import com.google.common.collect.ImmutableList;
import com.vaadin.data.Validator;
import com.vaadin.terminal.Resource;
import com.vaadin.terminal.StreamResource;
import com.vaadin.ui.Field;
import com.vaadin.ui.Form;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.VerticalLayout;
import commons.Streams;
import org.apache.commons.lang.StringUtils;
import ru.codeinside.gses.API;
import ru.codeinside.gses.activiti.ReadOnly;
import ru.codeinside.gses.activiti.forms.FormID;
import ru.codeinside.gses.activiti.forms.api.definitions.PropertyNode;
import ru.codeinside.gses.vaadin.JsonFormIntegration;
import ru.codeinside.gses.webui.ActivitiApp;
import ru.codeinside.gses.webui.form.api.FieldValuesSource;
import ru.codeinside.gses.webui.wizard.ExpandRequired;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
final public class JsonForm extends Form implements AsyncCompletable, ExpandRequired, FieldValuesSource {
final FormID formId;
final String templateRef;
final String valueId;
final JsonFormField formField;
AsyncCompleter asyncCompleter;
boolean hasAnyResult;
JsonFormIntegration integration;
String lastError;
String startValue;
boolean writable;
public JsonForm(FormID formId, String templateRef, String valueId, PropertyNode node, String value) {
super(new VerticalLayout());
this.formId = formId;
this.templateRef = templateRef;
this.valueId = valueId;
this.writable = node.isFieldWritable();
formField = new JsonFormField(valueId, node.getName());
startValue = value;
setSizeFull();
setImmediate(true);
VerticalLayout layout = (VerticalLayout) getLayout();
layout.setSizeFull();
}
@Override
public void attach() {
hasAnyResult = false;
VerticalLayout layout = (VerticalLayout) getLayout();
try {
integration = createIntegration();
layout.addComponent(integration);
layout.setExpandRatio(integration, 1f);
integration.setErrorReceiver(new ErrorReceiver());
integration.setValueReceiver(new ValueReceiver());
} catch (RuntimeException e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "ошибка загрузки шаблона", e);
layout.addComponent(new Label("Ошибка загрузки формы ввода данных " + e.getLocalizedMessage()));
}
super.attach();
}
@Override
public void detach() {
VerticalLayout layout = (VerticalLayout) getLayout();
if (integration != null) {
layout.removeComponent(integration);
integration = null;
}
super.detach();
}
@Override
public Field getField(Object propertyId) {
if (valueId.equals(propertyId)) {
TextArea field = new TextArea();
field.setNullRepresentation("");
field.setValue(formField.getValue());
return field;
}
if (API.JSON_FORM.equals(propertyId)) {
ReadOnly field = new ReadOnly(templateRef);
field.setCaption("Форма");
return field;
}
return null;
}
static String simplifyErrorStack(String value) {
value = StringUtils.trimToEmpty(value);
StringBuilder sb = new StringBuilder();
for (String part : value.split("\n")) {
if (part.contains("/ru.codeinside.gses.vaadin.WidgetSet/")) {
break;
}
if (sb.length() > 0) {
sb.append("<br/>\n");
}
sb.append(part);
}
return sb.toString();
}
@Override
public void commit() throws SourceException, Validator.InvalidValueException {
if (lastError != null) {
String msg = "Сбой " + lastError;
lastError = null;
hasAnyResult = false;
throw new Validator.InvalidValueException(msg);
}
if (!hasAnyResult) {
throw new Validator.InvalidValueException("Результат не получен!");
}
if (!formField.isValid()) {
hasAnyResult = false;
throw new Validator.InvalidValueException(formField.getValue());
}
}
@Override
public boolean isAsyncRequiredForComplete(AsyncCompleter asyncCompleter) {
if (hasAnyResult) {
return false;
}
if (asyncCompleter != null) {
this.asyncCompleter = asyncCompleter;
}
if (integration != null) {
integration.fireJson();
}
return true;
}
JsonFormIntegration createIntegration() {
String value = formField.isValid() ? formField.getValue() : startValue;
return createIntegration(formId, (ActivitiApp) getApplication(), templateRef, value, false);
}
public List<FormField> getFormFields() {
return ImmutableList.<FormField>of(formField, new JsonFormMarker("Форма", templateRef));
}
static String loadTemplate(ActivitiApp app, String ref) {
try {
URL serverUrl = app.getServerUrl();
URL url = new URL(serverUrl, ref);
logger().info("fetch template " + url);
URLConnection connection = url.openConnection();
connection.setDoOutput(false);
connection.setDoInput(true);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setUseCaches(false);
connection.connect();
return Streams.toString(connection.getInputStream());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static JsonFormIntegration createIntegration(FormID formId, ActivitiApp app, String ref, String value, boolean archive) {
String template = loadTemplate(app, ref);
String template1 = template.replace("'#archiveMode#'", Boolean.toString(archive));
boolean hasArchiveSupport = archive && !template1.equals(template);
template = template1.replace("'#jsonField#'", value == null ? "{}" : value);
StreamResource.StreamSource htmlStreamSource = new InMemoryResource(template);
Resource resource = new StreamResource(htmlStreamSource, "jf-" + formId + "-" + System.currentTimeMillis() + ".html", app);
JsonFormIntegration integration = new JsonFormIntegration();
integration.setSource(resource);
integration.setSizeFull();
integration.setImmediate(true);
if (archive) {
integration.setReadOnly(true);
if (!hasArchiveSupport) {
integration.setFixArchiveSupport(true);
}
}
return integration;
}
private static Logger logger() {
return Logger.getLogger(JsonForm.class.getName());
}
@Override
public Map<String, Object> getFieldValues() {
Map<String, Object> values = new LinkedHashMap<String, Object>();
if (writable) {
values.put(formField.id, formField.value);
}
return values;
}
final static class JsonFormField implements FormField, Serializable {
private final String id;
private final String name;
private String value;
public JsonFormField(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String getPropId() {
return id;
}
@Override
public String getName() {
return name;
}
void setValue(String value) {
this.value = value;
}
@Override
public String getValue() {
return value;
}
boolean isValid() {
return value != null && !value.isEmpty() && value.startsWith("{");
}
}
final static class JsonFormMarker implements FormField, Serializable {
private final String name;
private String value;
public JsonFormMarker(String name, String value) {
this.name = name;
this.value = value;
}
@Override
public String getPropId() {
return API.JSON_FORM;
}
@Override
public String getName() {
return name;
}
@Override
public String getValue() {
return value;
}
}
final static class InMemoryResource implements StreamResource.StreamSource {
final String html;
public InMemoryResource(String html) {
this.html = html;
}
public InputStream getStream() {
try {
return new ByteArrayInputStream(html.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
final class ErrorReceiver implements JsonFormIntegration.Receiver, Serializable {
@Override
public void onReceive(String value) {
hasAnyResult = true;
lastError = simplifyErrorStack(value);
asyncCompleter.onComplete(true);
}
}
final class ValueReceiver implements JsonFormIntegration.Receiver, Serializable {
@Override
public void onReceive(String value) {
value = StringUtils.trimToEmpty(value);
formField.setValue(value);
hasAnyResult = !value.isEmpty();
lastError = null;
if (hasAnyResult) {
startValue = null;
asyncCompleter.onComplete(true);
}
}
}
}