de.iwes.widgets.pattern.widget.patternedit.PatternCreator Maven / Gradle / Ivy
/**
* Copyright 2014-2018 Fraunhofer-Gesellschaft zur Förderung der angewandten Wissenschaften e.V.
*
* Licensed 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 de.iwes.widgets.pattern.widget.patternedit;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.ogema.core.application.ApplicationManager;
import org.ogema.core.model.Resource;
import org.ogema.core.model.schedule.Schedule;
import org.ogema.core.model.simple.FloatResource;
import org.ogema.core.resourcemanager.pattern.ContextSensitivePattern;
import org.ogema.core.resourcemanager.pattern.ResourcePattern;
import org.slf4j.LoggerFactory;
import de.iwes.widgets.api.extended.OgemaWidgetBase;
import de.iwes.widgets.api.extended.html.bricks.PageSnippet;
import de.iwes.widgets.api.widgets.OgemaWidget;
import de.iwes.widgets.api.widgets.WidgetGroup;
import de.iwes.widgets.api.widgets.WidgetPage;
import de.iwes.widgets.api.widgets.dynamics.TriggeredAction;
import de.iwes.widgets.api.widgets.dynamics.TriggeringAction;
import de.iwes.widgets.api.widgets.html.StaticTable;
import de.iwes.widgets.api.widgets.localisation.LocaleDictionary;
import de.iwes.widgets.api.widgets.localisation.OgemaLocale;
import de.iwes.widgets.api.widgets.sessionmanagement.OgemaHttpRequest;
import de.iwes.widgets.html.alert.Alert;
import de.iwes.widgets.html.alert.AlertData;
import de.iwes.widgets.html.form.button.Button;
import de.iwes.widgets.html.form.dropdown.DropdownOption;
import de.iwes.widgets.html.form.label.Label;
import de.iwes.widgets.html.form.textfield.TextField;
import de.iwes.widgets.pattern.page.impl.ContextUtil;
import de.iwes.widgets.pattern.page.impl.FilterRange;
import de.iwes.widgets.pattern.page.impl.LocalisationUtil;
import de.iwes.widgets.pattern.page.impl.PatternPageUtilInternal;
import de.iwes.widgets.pattern.page.impl.TypeSelectorDropdown;
import de.iwes.widgets.pattern.widget.patternedit.PatternPageAnnotations.DisplayValue;
import de.iwes.widgets.pattern.widget.patternedit.PatternPageAnnotations.Entry;
import de.iwes.widgets.pattern.widget.patternedit.PatternPageAnnotations.SetValue;
import de.iwes.widgets.resource.widget.autocomplete.ResourcePathAutocomplete;
/**
* A widget that allows the user to create resource pattern instances (see {@link ResourcePattern}).
* This only exists as a global widget, in particular the pattern type cannot be changed for individual
* users. Configuration for the individual pattern fields is possible via special annotations defined in
* {@link PatternPageAnnotations}.
*
* @param
* @param
* @param
*/
public class PatternCreator,R extends Resource, D extends LocaleDictionary> extends PageSnippet {
private static final long serialVersionUID = 1L;
private final Resource baseResource;
/**
* either a Class<? extends Resource>, or a Class<? extends ResourcePattern<?>>
*/
private final Class
pattern;
private final Class contextType;
private final ApplicationManager am;
private final Field[] fields;
private final Method[] staticMethods;
private final Field[] contextFields;
private final Collection> allowedTypes;
private final Label parentSelectorLabel;
private final ResourcePathAutocomplete parentSelector;
private final boolean addTypeSelector;
private final Label typeSelectorLabel;
private final TypeSelectorDropdown typeSelector;
private final Map> labelWidgets = new LinkedHashMap>();
private final Map> valueWidgets = new LinkedHashMap>();
private final Map references = new LinkedHashMap();
// values are String, Integer, Boolean, Long, for StringResource, IntegerResource, etc.
private final Map> fieldTypes = new LinkedHashMap>();
private final Map externalFilters = new LinkedHashMap();
private final Map externalRangeFilters = new LinkedHashMap();
@SuppressWarnings("unused")
private final WidgetGroup labelGroup;
private final WidgetGroup valueGroup;
private final Alert alert;
private final Button createButton;
private final Label nameLabel;
// this is either a TextField (if name is editable), or a Label (it it is non-editable)
private final OgemaWidget name;
public static ,R extends Resource, D extends LocaleDictionary, C> PatternCreator
getInstance(final WidgetPage page, final String id,
final Class pattern, final ApplicationManager am, final PatternCreatorConfiguration
config, final Class dictionary, final Class contextType) {
return AccessController.doPrivileged(new PrivilegedAction>() {
@Override
public PatternCreator run() {
return new PatternCreator
(page, id, pattern, am, config, dictionary, contextType);
}
});
}
// this construction is required for permission reasons
public static
,R extends Resource, D extends LocaleDictionary> PatternCreator
getInstance(final WidgetPage page, final String id,
final Class pattern, final ApplicationManager am, final PatternCreatorConfiguration
config, final Class dictionary) {
return AccessController.doPrivileged(new PrivilegedAction>() {
@Override
public PatternCreator run() {
return new PatternCreator
(page, id, pattern, am, config, dictionary, null);
}
});
}
private PatternCreator(WidgetPage page, String id, Class pattern, ApplicationManager am, PatternCreatorConfiguration
config, Class dictionary, Class contextType) {
this(page, id, pattern, am, config.getBaseResource(), config.getAdmissibleParentType(), config.isAllowNonToplevelParent(),
config.getAllowedTypes(), dictionary, config.getDefaultResourceName(), config.isAllowNonDefaultName(), contextType);
}
/**
*
* @param snippet
* @param pattern
* @param am
* @param baseResource
* @param allowedTypes may be null or empty, in which case only the base type R is allowed
* new patterns will be created below this node; may be null, in which case top-level resources are created
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private PatternCreator(WidgetPage page, String id, Class pattern, ApplicationManager am, Resource baseResource,
Class admissibleParentType, boolean allowNonToplevelParent, Collection> allowedTypes, Class dictionaryClass,
String defaultName, boolean allowNonDefault, Class contextType) {
super(page, id, true);
this.baseResource = baseResource;
this.pattern = pattern;
this.contextType = contextType;
if (contextType == null)
this.contextFields = null;
else {
final List fields = new ArrayList<>();
Entry e;
for (Field f : contextType.getDeclaredFields()) {
e = f.getAnnotation(Entry.class);
if (e == null)
continue;
fields.add(f);
}
this.contextFields = new Field[fields.size()];
fields.toArray(this.contextFields);
}
this.allowedTypes = allowedTypes;
if (allowedTypes != null && !allowedTypes.isEmpty())
addTypeSelector = true;
else
addTypeSelector = false;
this.am = am;
if (dictionaryClass == null)
dictionaryClass = (Class) LocaleDictionary.class;
this.alert = new Alert(page, id + "_alert", "");
alert.setDefaultVisibility(false);
this.nameLabel = new Label(page, id + "_nameSelectionLabel") {
private static final long serialVersionUID = 1L;
@Override
public void onGET(OgemaHttpRequest req) {
setText(LocalisationUtil.topResource(req.getLocale()), req);
}
};
nameLabel.setDefaultText("Top-level resource");
if (defaultName == null || allowNonDefault) {
// this.nameEditable = true;
this.name = new TextField(page, id + "_nameSelection");
if (defaultName != null)
((TextField) name).setDefaultValue(defaultName);
else
((TextField) name).setDefaultPlaceholder("Enter name");
}
else {
// this.nameEditable = false;
this.name = new Label(page, id + "_nameSelection", defaultName);
}
this.createButton = new SubmitButton(page, id + "_createButton","Create");
createButton.triggerAction(alert, TriggeringAction.POST_REQUEST, TriggeredAction.GET_REQUEST);
// add parent selector
if (baseResource == null && admissibleParentType != null) {
parentSelector = new ResourcePathAutocomplete(page, id + "_parentSelector", am.getResourceAccess());
parentSelector.setDefaultResourceType((Class) admissibleParentType); // TODO deal with pattern class
parentSelectorLabel = new Label(page, id + "_parentSelectorLabel", "Select parent resource") {
private static final long serialVersionUID = 1L;
@Override
public void onGET(OgemaHttpRequest req) {
OgemaLocale locale = req.getLocale();
String resType = parentSelector.getResourceType(req).getSimpleName();
setText(LocalisationUtil.parentSelectorLabel(resType,locale), req);
}
};
} else {
parentSelector = null;
parentSelectorLabel = null;
}
if (addTypeSelector) {
typeSelectorLabel = new Label(page, id + "_typeSelectorLabel","Select type") {
private static final long serialVersionUID = 1L;
@Override
public void onGET(OgemaHttpRequest req) {
OgemaLocale locale = req.getLocale();
setText(LocalisationUtil.typeSelectorLabel(locale), req);
}
};
typeSelector =
new TypeSelectorDropdown(page, id + "_typeSelectorDropdown", allowedTypes);
}
else {
typeSelectorLabel = null;
typeSelector = null;
}
// fields = pattern.getDeclaredFields();
fields = PatternPageUtilImpl.getResourceInfoRecursively(pattern);
staticMethods = getAnnotatedStaticMethods(pattern);
PatternPageUtilInternal.createWidgets(page, /*pattern.getSimpleName()*/ id, fields, staticMethods, contextFields, labelWidgets, valueWidgets, references, fieldTypes, externalFilters, externalRangeFilters, null, true, am, dictionaryClass, true, false);
buildPage();
if (labelWidgets.isEmpty()) {
labelGroup = null;
valueGroup = null;
} else {
labelGroup = page.registerWidgetGroup("labelWidgets", (Collection) labelWidgets.values());
valueGroup = page.registerWidgetGroup("valueWidgets", (Collection) valueWidgets.values());
this.triggerAction(valueGroup, TriggeringAction.PRE_POST_REQUEST, TriggeredAction.POST_REQUEST);
}
}
private class SubmitButton extends Button {
private static final long serialVersionUID = 1L;
public SubmitButton(WidgetPage page, String id, String defaultText) {
super(page, id, defaultText);
}
@Override
public void onPOSTComplete(String data, OgemaHttpRequest req) {
OgemaLocale locale = req.getLocale();
final P newPattern;
try {
String newResName;
if (name instanceof TextField)
newResName = ((TextField) name).getValue(req);
else
newResName = ((Label) name).getText(req);
if (!isValidJavaIdentifier(newResName)) throw new IllegalArgumentException(LocalisationUtil.invalidResourceName(newResName,locale));
PatternPageUtilInternal.applyInternalAndExternalFilters(fieldTypes, valueWidgets, externalFilters, externalRangeFilters, req); // throws exceptions that will be displayed on UI
final Object context = (contextType == null ? null : ContextUtil.createContext(contextType, contextFields, valueWidgets, am, req));
newPattern = createPattern(newResName, context, req);
if (newPattern == null) {
NullPointerException e = new NullPointerException("Pattern could not be created; create returned null");
am.getLogger().error("",e);
throw e;
}
// FIXME where was this used? To be removed...
if (contextType == null && newPattern instanceof ContextSensitivePattern) {
try {
((ContextSensitivePattern) newPattern).init();
} catch (Throwable e) {
LoggerFactory.getLogger(PatternPageAnnotations.class).error("Exception thrown in pattern init() method; type " + pattern.getSimpleName() + ": " + e);
}
}
Map exceptions = PatternPageUtilInternal.setValues(newPattern, fields, references, valueWidgets, req, am); // TODO same for editor
am.getResourcePatternAccess().activatePattern(newPattern);
newPattern.model.activate(true);
if (exceptions != null) {
StringBuilder sb =new StringBuilder();
if (exceptions.size() == 1) {
Map.Entry entry = exceptions.entrySet().iterator().next();
sb.append(entry.getValue());
}
else {
sb.append("There were multiple problems with the pattern values: "); // TODO localize
for (Map.Entry entry:exceptions.entrySet()) {
sb.append(entry.getKey() + ", ");
}
sb.append("failed");
}
alert.setText(sb.toString(), req);
alert.setStyle(AlertData.BOOTSTRAP_WARNING, req);
}
else {
alert.setText(LocalisationUtil.patternCreated(newResName, locale), req);
alert.setStyle(AlertData.BOOTSTRAP_SUCCESS, req);
}
} catch (Exception e) {
alert.setText(LocalisationUtil.patternNotCreated(locale) +": " + e, req);
alert.setStyle(AlertData.BOOTSTRAP_DANGER, req);
}
alert.setWidgetVisibility(true, req);
alert.autoDismiss(6000, req);
}
@Override
public void onGET(OgemaHttpRequest req) {
setText(LocalisationUtil.create(req.getLocale()), req);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private P createPattern(final String newResName, final Object context, final OgemaHttpRequest req) throws NoSuchMethodException, SecurityException,
InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
final Resource parent;
if (parentSelector != null) {
parent = parentSelector.getSelectedResource(req);
if (parent == null)
throw new IllegalStateException(LocalisationUtil.selectParentMessage(req.getLocale()));
}
else
parent = baseResource;
// FIXME required?
final Resource res = AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Resource run() {
if (parent == null)
return am.getResourceAccess().getResource(newResName);
else
return parent.getSubResource(newResName);
}
});
if (res != null && res.exists()) // FIXME what if the type of the non-existent but virtual resource is not compatible?
throw new IllegalArgumentException(LocalisationUtil.resourceExistsMessage(newResName, req.getLocale()));
Class selectedResType = null;
if (addTypeSelector) {
DropdownOption selected = typeSelector.getSelected(req);
if (selected != null) {
String type = selected.id();
try {
selectedResType = getTypeClass(type);
if (selectedResType == null) throw new RuntimeException("Class not among the allowed types");
} catch (Exception e) {
am.getLogger().error("Selected type "+ type + " could not be found.", e);
}
}
}
if (parent == null) {
// pattern management always tries to create pattern generic type resource,
// so we need to create the subtype resource before appliyng the pattern management method
if (selectedResType != null && Schedule.class.isAssignableFrom(selectedResType)) {
FloatResource fl = am.getResourceManagement().createResource(newResName, FloatResource.class);
fl.addDecorator("schedule", selectedResType);
if (context == null)
return am.getResourcePatternAccess().addDecorator(fl,"schedule", pattern);
else
return (P) am.getResourcePatternAccess().addDecorator(fl,"schedule", (Class) pattern, context);
}
else if (selectedResType != null)
am.getResourceManagement().createResource(newResName, selectedResType);
if (context == null)
return am.getResourcePatternAccess().createResource(newResName, pattern);
else
return (P) am.getResourcePatternAccess().createResource(newResName, (Class) pattern, context);
}
else {
if (selectedResType != null && Schedule.class.isAssignableFrom(selectedResType)) {
FloatResource fl = parent.addDecorator(newResName, FloatResource.class); // TODO allow other types than Float?
fl.addDecorator("schedule", selectedResType);
if (context == null)
return am.getResourcePatternAccess().addDecorator(fl,"schedule", pattern);
else
return (P) am.getResourcePatternAccess().addDecorator(fl,"schedule", (Class) pattern, context);
}
else if (selectedResType != null)
parent.addDecorator(newResName, selectedResType);
if (context == null)
return am.getResourcePatternAccess().addDecorator(parent, newResName, pattern);
else
return (P) am.getResourcePatternAccess().addDecorator(parent, newResName, (Class) pattern, context);
}
}
private Class getTypeClass(String className) {
for (Class type : allowedTypes) {
if (type.getName().equals(className)) return type;
}
return null;
}
private final void buildPage() {
append(alert, null);
int[] sizes = new int[] { 2, 4 };
int nrRows = labelWidgets.size()+1;
if (addTypeSelector)
nrRows++;
if (parentSelector != null)
nrRows++;
StaticTable table = new StaticTable(nrRows, 2, sizes);
if (parentSelector != null)
addLine(table, parentSelectorLabel, parentSelector);
addLine(table, nameLabel, (OgemaWidgetBase) name);
if (addTypeSelector)
addLine(table, typeSelectorLabel, typeSelector);
for (Map.Entry> entry : labelWidgets.entrySet()) {
String id = entry.getKey();
OgemaWidgetBase label = entry.getValue();
OgemaWidgetBase value = valueWidgets.get(id);
addLine(table, label, value);
}
append(table, null);
append(createButton, null);
}
private int lineCounter = 0;
private void addLine(StaticTable table, OgemaWidgetBase labelWidget, OgemaWidgetBase valueWidget) {
table.setContent(lineCounter, 0, labelWidget).setContent(lineCounter++, 1, valueWidget);
}
private static Method[] getAnnotatedStaticMethods(Class> pattern) {
Method[] methods = pattern.getDeclaredMethods();
List methodList = new ArrayList();
for (Method method:methods) {
if (!Modifier.isStatic(method.getModifiers())) continue;
DisplayValue dv = method.getAnnotation(DisplayValue.class);
SetValue sv = method.getAnnotation(SetValue.class);
if (dv != null || sv != null) methodList.add(method);
}
Method[] result = new Method[methodList.size()];
int counter = 0;
for (Method method: methodList) {
result[counter++] = method;
}
return result;
}
private static boolean isValidJavaIdentifier(String s) {
if (s == null || s.isEmpty()) {
return false;
}
if (!Character.isJavaIdentifierStart(s.charAt(0))) {
return false;
}
for (int i = 1; i < s.length(); i++) {
if (!Character.isJavaIdentifierPart(s.charAt(i))) {
return false;
}
}
return true;
}
/**
* @return
* the submit button, by means of which changes in the selected pattern are saved.
*/
public Button getSubmitButton() {
return createButton;
}
}