package no.met.metadataeditor.widget; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSeeAlso; import no.met.metadataeditor.EditorException; import no.met.metadataeditor.EditorWidgetView; import no.met.metadataeditor.InvalidEditorConfigurationException; import no.met.metadataeditor.dataTypes.EditorVariable; import no.met.metadataeditor.dataTypes.EditorVariableContent; import no.met.metadataeditor.dataTypes.attributes.DataAttribute; /** * Base class for widgets. Contains most of the logic for working with the widgets. */ @XmlRootElement @XmlSeeAlso({ LatLonBoundingBoxWidget.class, ListWidget.class, StartAndStopTimeWidget.class, StringWidget.class, UriWidget.class, TextAreaWidget.class, TimeWidget.class, MultiSelectListWidget.class, StringAndListWidget.class, OnlineResourceWidget.class, ContainerWidget.class, KeyValueListWidget.class, DateWidget.class, XMDInfoWidget.class, XMDDisplayAreaWidget.class, XMDWmsInfoWidget.class, XMDProjectionDatasetWidget.class, XMDProjectionFimexWidget.class, AutoUUIDWidget.class, NowDateWidget.class, MetnoDatasetIdentifierWidget.class, SkosListWidget.class, PolygonBoundingBoxWidget.class, SkosControlledVocabWidget.class }) public abstract class EditorWidget implements Serializable { private static final long serialVersionUID = -2532825684273483564L; // the variable name that is used for this field. Must correspond with the // same name in the template private String variableName; private String label; private String description; private boolean isPopulated = false; private int maxOccurs = 1; private int minOccurs = 1; private URI resourceUri; private List<EditorWidget> children = new ArrayList<>(); private List<EditorWidgetView> widgetViews = new ArrayList<>(); private Class<? extends DataAttribute> attributeClass; public EditorWidget() { } public EditorWidget(EditorWidget cloneFrom){ this.variableName = cloneFrom.variableName; this.label = cloneFrom.label; this.description = cloneFrom.description; this.maxOccurs = cloneFrom.maxOccurs; this.minOccurs = cloneFrom.minOccurs; this.resourceUri = cloneFrom.resourceUri; this.attributeClass = cloneFrom.attributeClass; this.children = new ArrayList<>(); Collections.copy(this.children, cloneFrom.children); } public EditorWidget(String label, String variableName) { this.label = label; this.variableName = variableName; } @XmlAttribute public String getVariableName() { return variableName; } public void setVariableName(String variableName) { this.variableName = variableName; } @XmlElement(namespace="http://www.met.no/schema/metadataeditor/editorConfiguration") public String getDescription(){ return description; } public void setDescription(String description){ this.description = description; } @XmlAttribute public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public void setMinOccurs(int minOccurs){ this.minOccurs = minOccurs; } public void setMaxOccurs(int maxOccurs){ this.maxOccurs = maxOccurs; } public Class<? extends DataAttribute> getAttributeClass() { return attributeClass; } public void setAttributeClass(Class<? extends DataAttribute> attributeClass) { this.attributeClass = attributeClass; } /** * Add configuration from the editor variable to the widget and the widgets children. * @param variable * @throws InvalidEditorConfigurationException Thrown if a child widget refers to a variable not * found in the child variables or the widget configuration is not valid. * @return Always returns true */ public boolean configure(EditorVariable variable) { maxOccurs = variable.getMaxOccurs(); minOccurs = variable.getMinOccurs(); attributeClass = variable.getDataAttributes().getClass(); setResourceUri(variable.getDefaultResourceURI()); Map<String,EditorVariable> childVarMap = variable.getChildren(); for( EditorWidget child : children ){ String varName = child.getVariableName(); if( !childVarMap.containsKey(varName)){ throw new InvalidEditorConfigurationException( varName + " is not found in the template.", InvalidEditorConfigurationException.UNKNOWN_VARIABLE ); } EditorVariable ev = childVarMap.get(varName); child.configure(ev); } // throws validateConfiguration(); return true; } /** * Validate that the configuration of the widget is valid or not. The base class implementation never * throws an exception. * @throws InvalidEditorConfigurationException If the configuration is not valid it will throw an exception. */ protected void validateConfiguration() { // do nothing in base class } public void generateWidgetViews(List<EditorVariableContent> contents){ for (EditorVariableContent content : contents) { DataAttribute attrs = content.getAttrs(); Map<String,String> values = attrs.getAttributes(getRelevantAttributes().toArray(new String[0])); EditorWidgetView view = createWidgetView(values); widgetViews.add(view); // recursively populate all children Map<String, List<EditorVariableContent>> childContentMap = content.getChildren(); for( Map.Entry<String, List<EditorVariableContent>> entry : childContentMap.entrySet() ){ String varName = entry.getKey(); List<EditorVariableContent> childContent = entry.getValue(); if( view.hasChildWidget(varName) ){ EditorWidget childWidget = view.getChildWidget(varName); childWidget.generateWidgetViews(childContent); } } } // add extra widget views to ensure that we have as many as minOccurs demands while( widgetViews.size() < minOccurs ){ addNewValue(); } isPopulated = true; } EditorWidgetView createWidgetView(Map<String, String> values){ EditorWidgetView view = new EditorWidgetView(); List<EditorWidget> viewChildWidgets = new ArrayList<>(); for( EditorWidget child : children ){ viewChildWidgets.add(cloneInstance(child)); } view.setChildren(viewChildWidgets); view.setValues(values); view.setDataAttributeClass(attributeClass); return view; } /** * Take the information stored in the widget and send it back to the * EditorVariable */ public List<EditorVariableContent> getContent() { List<EditorVariableContent> contentList = new ArrayList<>(); for( EditorWidgetView view : widgetViews ){ EditorVariableContent content = getContentForView(view); contentList.add(content); // recursively get content from child widgets. Map<String, List<EditorVariableContent>> childContentMap = new HashMap<>(); for( EditorWidget child : children){ String varName = child.getVariableName(); if( view.hasChildWidget(varName)) { EditorWidget childWidget = view.getChildWidget(varName); List<EditorVariableContent> childContent = childWidget.getContent(); childContentMap.put(varName, childContent); } } content.setChildren(childContentMap); } return contentList; } protected EditorVariableContent getContentForView(EditorWidgetView view){ EditorVariableContent content = new EditorVariableContent(); DataAttribute da = view.valuesAsAttriubte(); content.setAttrs(da); return content; } private List<String> getRelevantAttributes() { Map<String, String> defaultValues = getDefaultValue(); List<String> relevantAttributes = new ArrayList<>(); for (Map.Entry<String, String> entry : defaultValues.entrySet()) { relevantAttributes.add(entry.getKey()); } return relevantAttributes; } public void addNewValue() { EditorWidgetView view = createWidgetView(getDefaultValue()); widgetViews.add(view); } public String getWidgetType() { return getClass().getName(); } public int getMaxOccurs() { return maxOccurs; } public int getMinOccurs() { return minOccurs; } public URI getResourceUri() { return resourceUri; } public void setResourceUri(URI resourceUri) { this.resourceUri = resourceUri; } public boolean isPopulated() { return isPopulated; } public abstract Map<String, String> getDefaultValue(); @XmlElement(name="widget", namespace="http://www.met.no/schema/metadataeditor/editorConfiguration") public List<EditorWidget> getChildren() { return children; } public void setChildren(List<EditorWidget> children) { this.children = children; } /** * Convert the widget tree under this editor widget to a list. * @return */ public List<EditorWidget> getWidgetTreeAsList() { List<EditorWidget> widgetTree = new ArrayList<>(); for( EditorWidget child : children ){ widgetTree.add(child); widgetTree.addAll(child.getWidgetTreeAsList()); } return widgetTree; } public List<EditorWidgetView> getWidgetViews() { return widgetViews; } public void setWidgetViews(List<EditorWidgetView> widgetViews) { this.widgetViews = widgetViews; } /** * Clone an instance of an EditorWidget. Will clone correctly for subclasses as well. * @param cloneFrom * @return */ private EditorWidget cloneInstance(EditorWidget cloneFrom){ Class<? extends EditorWidget> cls = cloneFrom.getClass(); try { Constructor<EditorWidget> ctr = getCopyConstructor(cls); EditorWidget widget = ctr.newInstance(cloneFrom); return widget; } catch (SecurityException e) { String msg = "No access to copy constructor for class: " + cls.getName(); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } catch (InstantiationException e) { String msg = "Failed to execute copy constructor for class: " + cls.getName(); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } catch (IllegalAccessException e) { String msg = "No access to copy constructor for class: " + cls.getName(); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } catch (IllegalArgumentException e) { String msg = "Illegal argument to copy constructor for class: " + cls.getName(); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } catch (InvocationTargetException e) { String msg = "Wrong invocation target for copy constructor for class: " + cls.getName(); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } } private Constructor<EditorWidget> getCopyConstructor(Class<? extends EditorWidget> cls){ try { @SuppressWarnings("unchecked") Constructor<EditorWidget> ctr = (Constructor<EditorWidget>) cls.getConstructor(cls); return ctr; } catch (NoSuchMethodException e) { String msg = "No copy constructor for class: " + cls.getName(); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } catch (SecurityException e) { String msg = "No access to copy constructor for class: " + cls.getName(); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } } public void removeValue(EditorWidgetView widgetView) { widgetViews.remove(widgetView); } }