package org.jboss.as.console.mbui.widgets;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import org.jboss.as.console.client.shared.help.StaticHelpPanel;
import org.jboss.ballroom.client.rbac.SecurityContext;
import org.jboss.ballroom.client.widgets.forms.CheckBoxItem;
import org.jboss.ballroom.client.widgets.forms.ComboBoxItem;
import org.jboss.ballroom.client.widgets.forms.DisclosureGroupRenderer;
import org.jboss.ballroom.client.widgets.forms.FormItem;
import org.jboss.ballroom.client.widgets.forms.ListItem;
import org.jboss.ballroom.client.widgets.forms.NumberBoxItem;
import org.jboss.ballroom.client.widgets.forms.TextBoxItem;
import org.jboss.dmr.client.ModelNode;
import org.jboss.dmr.client.ModelType;
import org.jboss.dmr.client.Property;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Heiko Braun
* @date 02/07/14
*/
public class ModelNodeFormBuilder {
private ModelNodeForm form;
private SecurityContext securityContext;
private String address;
private ModelNode modelDescription;
private String[] attributeNames;
private SafeHtml help;
private boolean runtimeAttributes = true;
private boolean configAttributes = true;
private boolean requiredOnly;
private boolean createMode;
public ModelNodeFormBuilder setSecurityContext(SecurityContext sc) {
this.securityContext = sc;
return this;
}
public ModelNodeFormBuilder setAddress(String addr) {
this.address = addr;
return this;
}
public ModelNodeFormBuilder setResourceDescription(ModelNode resourceDescription) {
this.modelDescription = resourceDescription;
return this;
}
public ModelNodeFormBuilder setRuntimeOnly() {
this.configAttributes = false;
this.runtimeAttributes= true;
return this;
}
public ModelNodeFormBuilder setConfigOnly() {
this.configAttributes = true;
this.runtimeAttributes = false;
return this;
}
public ModelNodeFormBuilder setFields(String... attributeName) {
this.attributeNames = attributeName;
return this;
}
public FormAssets build()
{
// pre-requisite
if(createMode && !modelDescription.hasDefined("operations"))
{
throw new IllegalStateException("Operation descriptions not defined");
}
this.form = new ModelNodeForm(this.address, this.securityContext);
this.form.setNumColumns(2);
this.form.setEnabled(false);
assert modelDescription.hasDefined("attributes") : "Invalid model description. Expected child 'attributes'";
List<Property> attributeDescriptions = new ArrayList<Property>();
if(createMode && modelDescription.get("operations").get("add").hasDefined("request-properties"))
{
attributeDescriptions = modelDescription.get("operations").get("add").get("request-properties").asPropertyList();
}
else if(!createMode)
{
attributeDescriptions = modelDescription.get("attributes").asPropertyList();
}
// sort fields
Collections.sort(attributeDescriptions, new Comparator<Property>() {
@Override
public int compare(Property property, Property property1) {
return property.getName().compareTo(property1.getName());
}
});
// catch-all directive, if no explicit attributes given
if(null == attributeNames)
{
attributeNames = new String[attributeDescriptions.size()];
int i=0;
for(Property attr : attributeDescriptions)
{
attributeNames[i] = attr.getName();
i++;
}
}
LinkedList<FormItem> requiredItems = new LinkedList<FormItem>();
LinkedList<FormItem> optionalItems = new LinkedList<FormItem>();
SafeHtmlBuilder helpTexts = new SafeHtmlBuilder();
helpTexts.appendHtmlConstant("<table class='help-attribute-descriptions'>");
Map<String, ModelNode> defaultValues = new HashMap<String, ModelNode>();
int numWritable = 0;
boolean hasRequired = false;
// for some decision below we need to know wether or not required attributes exist at all
if(requiredOnly) {
for (Property attr : attributeDescriptions) {
ModelNode value = attr.getValue();
boolean required = value.hasDefined("nillable") ? !value.get("nillable").asBoolean() : false;
boolean readOnly = value.get("access-type").asString().equals("read-only");
if (required & !readOnly) {
hasRequired = true;
break;
}
}
}
for (String attribute : attributeNames)
{
for(Property attr : attributeDescriptions)
{
boolean isRuntime = attr.getValue().get("storage").asString().equals("runtime");
boolean isConfig = !attr.getValue().get("storage").asString().equals("runtime"); // TODO: verify statement
if(runtimeAttributes == false && isRuntime)
{
continue;
}
if(configAttributes == false && isConfig)
{
continue;
}
if(!attr.getName().equals(attribute))
continue;
// -------
// Attribute meta data
// name
char[] attrName = attr.getName().toCharArray();
attrName[0] = Character.toUpperCase(attrName[0]);
// field label
String label = new String(attrName).replace("-", " ");
ModelNode attrDesc = attr.getValue();
// type
ModelType type = ModelType.valueOf(attrDesc.get("type").asString().toUpperCase());
// default value
if(attrDesc.hasDefined("default")) {
ModelNode defaultValue = attrDesc.get("default");
ModelNode value = new ModelNode();
//value.set(type, ModelNodeForm.downCast(defaultValue));
setValue(value, type, ModelNodeForm.downCast(defaultValue)); // workaround for numeric types
defaultValues.put(attr.getName(), value);
}
// read-only
final boolean readOnly = attrDesc.hasDefined("access-type") ?
attrDesc.get("access-type").asString().equals("read-only"): false;
// nillable
boolean isRequired = attrDesc.hasDefined("nillable") ? !attrDesc.get("nillable").asBoolean() : false;
// createMode flag
if((createMode && readOnly) )
continue;
// requiredOnly flag
if(requiredOnly && hasRequired && !isRequired)
continue;
// count writable attributes
if(!readOnly && !isRuntime) numWritable++;
// -------
// help
helpTexts.appendHtmlConstant("<tr class='help-field-row'>");
helpTexts.appendHtmlConstant("<td class='help-field-name'>");
helpTexts.appendEscaped(label).appendEscaped(": ");
helpTexts.appendHtmlConstant("</td>");
helpTexts.appendHtmlConstant("<td class='help-field-desc'>");
try {
String descWorkaround = attrDesc.get("description").asString();
helpTexts.appendHtmlConstant(descWorkaround.equals("null") ? "n/a" : descWorkaround);
} catch (Throwable e) {
// ignore parse errors
helpTexts.appendHtmlConstant("<i>Failed to parse description</i>");
}
helpTexts.appendHtmlConstant("</td>");
helpTexts.appendHtmlConstant("</tr>");
FormItem formItem = null;
switch(type)
{
case BOOLEAN:
formItem = new CheckBoxItem(attr.getName(), label);
formItem.setEnabled(!readOnly && !isRuntime);
break;
case DOUBLE:
formItem = new NumberBoxItem(attr.getName(), label);
formItem.setRequired(isRequired);
formItem.setEnabled(!readOnly && !isRuntime);
break;
case LONG:
formItem = new NumberBoxItem(attr.getName(), label);
formItem.setRequired(isRequired);
formItem.setEnabled(!readOnly&& !isRuntime);
break;
case BIG_DECIMAL:
formItem = new NumberBoxItem(attr.getName(), label);
formItem.setRequired(isRequired);
formItem.setEnabled(!readOnly&& !isRuntime);
break;
case INT:
formItem = new NumberBoxItem(attr.getName(), label);
formItem.setRequired(isRequired);
formItem.setEnabled(!readOnly&& !isRuntime);
break;
case LIST:
formItem = new ListItem(attr.getName(), label);
formItem.setRequired(isRequired);
formItem.setEnabled(!readOnly&& !isRuntime);
break;
case STRING:
if(attrDesc.get("allowed").isDefined())
{
List<ModelNode> allowed = attrDesc.get("allowed").asList();
Set<String> allowedValues = new HashSet<String>(allowed.size());
for(ModelNode value : allowed)
allowedValues.add(value.asString());
ComboBoxItem combo = new ComboBoxItem(attr.getName(), label);
combo.setValueMap(allowedValues);
combo.setEnabled(!readOnly && !isRuntime);
combo.setRequired(isRequired);
formItem = combo;
}
else
{
TextBoxItem textBoxItem = new TextBoxItem(attr.getName(), label);
textBoxItem.setAllowWhiteSpace(true);
textBoxItem.setRequired(isRequired);
textBoxItem.setEnabled(!readOnly && !isRuntime);
formItem = textBoxItem;
}
// TODO: Support for TextAreaItem
break;
default:
Log.error("Unsupported ModelType " + type + ", attribute '"+attr.getName()+"'");
}
if(formItem!=null)
{
if(createMode) {
if(isRequired)
requiredItems.add(formItem);
else
optionalItems.add(formItem);
}
else
{
requiredItems.add(formItem);
}
}
}
}
// distinguish required and optional fields (createMode)
if(requiredItems.isEmpty())
{
// no required fields explicitly given, treat all fields as required
if(createMode)
{
optionalItems.addFirst(new TextBoxItem("name", "Name", true));
numWritable++;
}
form.setFields(optionalItems.toArray(new FormItem[]{}));
}
else
{
if(createMode)
{
requiredItems.addFirst(new TextBoxItem("name", "Name", true));
numWritable++;
}
form.setFields(requiredItems.toArray(new FormItem[]{}));
if(optionalItems.size()>0)
form.setFieldsInGroup("Optional Fields", new DisclosureGroupRenderer(), optionalItems.toArray(new FormItem[]{}));
}
// form meta data
form.setDefaults(defaultValues);
form.setHasWritableAttributes(numWritable > 0);
return new FormAssets(form, helpTexts.toSafeHtml());
}
public ModelNodeFormBuilder setRequiredOnly(boolean requiredOnly) {
this.requiredOnly = requiredOnly;
return this;
}
/**
* In create mode we consider the paramter for the 'add' operations for building the form.
*
* @param createMode
* @return
*/
public ModelNodeFormBuilder setCreateMode(boolean createMode) {
this.createMode = createMode;
return this;
}
public final class FormAssets {
private ModelNodeForm form;
private SafeHtml help;
public FormAssets(ModelNodeForm form, SafeHtml help) {
this.form = form;
this.help = help;
}
public ModelNodeForm getForm() {
return form;
}
public StaticHelpPanel getHelp() {
return new StaticHelpPanel(help);
}
}
/**
* a more lenient way to update calues by type
*/
public static void setValue(ModelNode target, ModelType type, Object propValue ) {
if(type.equals(ModelType.STRING))
{
target.set((String)propValue);
}
else if(type.equals(ModelType.INT))
{
target.set((Integer)propValue);
}
else if(type.equals(ModelType.DOUBLE))
{
target.set((Double)propValue);
}
else if(type.equals(ModelType.LONG))
{
// in some cases the server returns the wrong model type for numeric values
// i.e the description affords a ModelType.LONG, but a ModelType.INTEGER is returned
try {
target.set((Long)propValue);
} catch (Throwable e) { // ClassCastException
target.set(Integer.valueOf((Integer)propValue));
}
}
else if(type.equals(ModelType.BIG_DECIMAL))
{
// in some cases the server returns the wrong model type for numeric values
// i.e the description affords a ModelType.LONG, but a ModelType.INTEGER is returned
try {
target.set((BigDecimal)propValue);
} catch (Throwable e) { // ClassCastException
target.set(Double.valueOf((Double)propValue));
}
}
else if(type.equals(ModelType.BOOLEAN))
{
target.set((Boolean)propValue);
}
else if(type.equals(ModelType.LIST))
{
target.setEmptyList();
List list = (List)propValue;
for(Object item : list)
target.add(String.valueOf(item));
}
else
{
throw new RuntimeException("Type conversion not implemented for "+type);
}
}
}