/**
* Licensed to the Austrian Association for Software Tool Integration (AASTI)
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. The AASTI licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openengsb.ui.common.editor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormValidatingBehavior;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.extensions.ajax.markup.html.AjaxEditableLabel;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.validation.AbstractFormValidator;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.validation.ValidationError;
import org.openengsb.core.api.ConnectorValidationFailedException;
import org.openengsb.core.api.descriptor.AttributeDefinition;
import org.openengsb.core.api.validation.FormValidator;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
/**
* Creates a panel containing a service-editor, for usage in forms.
*
*/
public class ServiceEditorPanel extends Panel {
private static final long serialVersionUID = 5593901084329552949L;
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceEditorPanel.class);
private final class EntryModel implements IModel<String> {
private static final long serialVersionUID = -6996584309100143740L;
private final Entry<String, Object> entry;
private final int index;
public EntryModel(Entry<String, Object> entry, int i) {
this.entry = entry;
index = i;
adjustArraySize();
}
private void adjustArraySize() {
if (isArray()) {
if (getArray().length <= index) {
Object[] newArray = Arrays.copyOf(getArray(), index + 1);
entry.setValue(newArray);
}
} else if (index > 0) {
entry.setValue(new Object[index + 1]);
}
}
public void deleteSubElement(int index) {
if (isArray()) {
Object[] oldArray = getArray();
Object[] newArray = new Object[oldArray.length - 1];
int j = 0;
for (int i = 0; i < oldArray.length; i++) {
if (i == index) {
continue;
}
newArray[j] = oldArray[i];
j++;
}
entry.setValue(newArray.length == 1 ? newArray[0] : newArray);
}
}
@Override
public void detach() {
// do nothing
}
@Override
public String getObject() {
if (isArray()) {
return (String) getArray()[index];
} else if (index == 0) {
Object entryValue = entry.getValue();
return entryValue == null ? "null" : entryValue.toString();
} else {
throw new IllegalStateException("value is not an array");
}
}
@Override
public void setObject(String object) {
if (!isArray() && index == 0) {
entry.setValue(object);
} else {
Object[] array = getArray();
array[index] = object;
}
}
private Object[] getArray() {
return (Object[]) entry.getValue();
}
private boolean isArray() {
return entry.getValue().getClass().isArray();
}
}
private final List<AttributeDefinition> attributes;
private Model<Boolean> validatingModel;
private WebMarkupContainer propertiesContainer;
private ListView<MapEntry<String, Object>> propertiesList;
private final Form<?> parentForm;
private Map<String, Object> properties;
private static final List<String> LOCKED_PROPERTIES = Arrays.asList(
org.openengsb.core.api.Constants.CONNECTOR_KEY,
org.openengsb.core.api.Constants.DOMAIN_KEY,
Constants.SERVICE_ID, Constants.OBJECTCLASS, Constants.SERVICE_PID);
@SuppressWarnings({ "serial" })
public ServiceEditorPanel(String id, List<AttributeDefinition> attributes,
Map<String, String> attributeMap, Map<String, Object> properties, Form<?> parentForm) {
super(id);
this.attributes = attributes;
this.parentForm = parentForm;
this.properties = properties;
initPanel(attributes, attributeMap, properties);
add(new Behavior() {
@Override
public void renderHead(Component component, IHeaderResponse response) {
response.render(CssHeaderItem.forReference(new PackageResourceReference(ServiceEditorPanel.class,
"ServiceEditorPanel.css")));
}
});
}
public void reloadList(Map<String, Object> properties) {
List<MapEntry<String, Object>> entryList = transformToEntryList(properties);
Collections.sort(entryList, new Comparator<MapEntry<String, Object>>() {
@Override
public int compare(MapEntry<String, Object> o1, MapEntry<String, Object> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
this.properties = properties;
propertiesList.setList(entryList);
}
public List<MapEntry<String, Object>> transformToEntryList(Map<String, Object> properties) {
Set<Entry<String, Object>> entrySet = properties.entrySet();
Collection<Entry<String, Object>> filtered =
Collections2.filter(entrySet, new Predicate<Entry<String, Object>>() {
@Override
public boolean apply(Entry<String, Object> input) {
return !LOCKED_PROPERTIES.contains(input.getKey());
}
});
Collection<MapEntry<String, Object>> transformed =
Collections2.transform(filtered, new EntryConverterFunction<String, Object>(properties));
List<MapEntry<String, Object>> entryList = Lists.newArrayList(transformed);
return entryList;
}
private class EntryConverterFunction<K, V> implements Function<Map.Entry<K, V>, MapEntry<K, V>> {
private final Map<K, V> originalMap;
protected EntryConverterFunction(Map<K, V> originalMap) {
this.originalMap = originalMap;
}
@Override
public MapEntry<K, V> apply(Entry<K, V> input) {
return new MapEntry<K, V>(originalMap, input);
}
}
/**
* Removes the property with the given key and reloads the property list afterwards
*/
public void removeProperty(String key) {
properties.remove(key);
reloadList(properties);
}
@SuppressWarnings("serial")
private void initPanel(List<AttributeDefinition> attributes, Map<String, String> attributeMap,
Map<String, Object> properties) {
RepeatingView fields =
AttributeEditorUtil.createFieldList("fields", attributes, attributeMap);
add(fields);
setOutputMarkupId(true);
validatingModel = new Model<Boolean>(true);
CheckBox checkbox = new CheckBox("validate", validatingModel);
add(checkbox);
propertiesList = new ListView<MapEntry<String, Object>>("properties") {
@Override
protected void populateItem(final ListItem<MapEntry<String, Object>> item) {
item.setOutputMarkupId(true);
final MapEntry<String, Object> modelObject = item.getModelObject();
IModel<String> keyModel = new PropertyModel<String>(modelObject, "key");
item.add(new Label("key", keyModel));
item.add(new WebMarkupContainer("buttonKey").add(new AjaxEventBehavior("onclick") {
@Override
protected void onEvent(AjaxRequestTarget target) {
ServiceEditorPanel.this.removeProperty(modelObject.getKey());
target.add(ServiceEditorPanel.this);
}
}));
final RepeatingView repeater = new RepeatingView("values");
item.add(repeater);
Object value = modelObject.getValue();
if (value.getClass().isArray()) {
Object[] values = (Object[]) value;
for (int i = 0; i < values.length; i++) {
WebMarkupContainer container = new WebMarkupContainer(repeater.newChildId());
final EntryModel model = new EntryModel(modelObject, i);
final int index = i;
AjaxEditableLabel<String> l =
new AjaxEditableLabel<String>("value", model);
container.add(l);
container.add(new WebMarkupContainer("buttonValue").add(new AjaxEventBehavior("onclick") {
@Override
protected void onEvent(AjaxRequestTarget target) {
model.deleteSubElement(index);
target.add(ServiceEditorPanel.this);
}
}));
repeater.add(container);
}
} else {
WebMarkupContainer container = new WebMarkupContainer(repeater.newChildId());
IModel<String> valueModel = new EntryModel(modelObject, 0);
AjaxEditableLabel<String> l = new AjaxEditableLabel<String>("value", valueModel);
container.add(l);
container.add(new WebMarkupContainer("buttonValue").setVisible(false));
repeater.add(container);
}
AjaxButton button = new AjaxButton("newArrayEntry", parentForm) {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
Object value = modelObject.getValue();
if (value.getClass().isArray()) {
Object[] array = (Object[]) value;
Object[] newArray = Arrays.copyOf(array, array.length + 1);
newArray[array.length] = "";
modelObject.setValue(newArray);
} else {
Object[] newArray = new Object[2];
newArray[0] = value;
newArray[1] = "";
modelObject.setValue(newArray);
}
target.add(item);
target.add(ServiceEditorPanel.this);
}
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
LOGGER.warn("Error occured during submit!");
}
};
item.add(button);
}
};
propertiesList.setOutputMarkupId(true);
reloadList(properties);
propertiesContainer = new WebMarkupContainer("propertiesContainer");
propertiesContainer.add(propertiesList);
propertiesContainer.setOutputMarkupId(true);
add(propertiesList);
}
/**
* attach a ServiceValidator to the given form. This formValidator is meant to validate the fields in context to
* each other. This validation is only done on submit.
*/
public void attachFormValidator(final Form<?> form, final FormValidator validator) {
form.add(new AbstractFormValidator() {
private static final long serialVersionUID = -4181095793820830517L;
@Override
public void validate(Form<?> form) {
Map<String, FormComponent<?>> loadFormComponents = loadFormComponents(form);
Map<String, String> toValidate = new HashMap<String, String>();
for (Map.Entry<String, FormComponent<?>> entry : loadFormComponents.entrySet()) {
toValidate.put(entry.getKey(), entry.getValue().getValue());
}
try {
validator.validate(toValidate);
} catch (ConnectorValidationFailedException e) {
Map<String, String> attributeErrorMessages = e.getErrorMessages();
for (Map.Entry<String, String> entry : attributeErrorMessages.entrySet()) {
FormComponent<?> fc = loadFormComponents.get(entry.getKey());
fc.error(new ValidationError().setMessage(entry.getValue()));
}
}
}
@Override
public FormComponent<?>[] getDependentFormComponents() {
Collection<FormComponent<?>> formComponents = loadFormComponents(form).values();
return formComponents.toArray(new FormComponent<?>[formComponents.size()]);
}
private Map<String, FormComponent<?>> loadFormComponents(final Form<?> form) {
Map<String, FormComponent<?>> formComponents = new HashMap<String, FormComponent<?>>();
if (validator != null) {
for (String attribute : validator.fieldsToValidate()) {
Component component =
form.get("attributesPanel:fields:" + attribute + ":row:field");
if (component instanceof FormComponent<?>) {
formComponents.put(attribute, (FormComponent<?>) component);
}
}
}
return formComponents;
}
});
}
/**
* enable ad-hoc validation on all fields in your form
*/
public static void addAjaxValidationToForm(Form<?> form) {
AjaxFormValidatingBehavior.addToAllFormComponents(form, "onBlur");
AjaxFormValidatingBehavior.addToAllFormComponents(form, "onChange");
}
public List<AttributeDefinition> getAttributes() {
return attributes;
}
public boolean isValidating() {
return validatingModel.getObject();
}
}