package no.met.metadataeditor.dataTypes; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import no.met.metadataeditor.EditorException; import no.met.metadataeditor.dataTypes.attributes.ContainerAttribute; import no.met.metadataeditor.dataTypes.attributes.DataAttribute; import no.met.metadataeditor.dataTypes.attributes.DateAttribute; import no.met.metadataeditor.dataTypes.attributes.KeyValueListAttribute; import no.met.metadataeditor.dataTypes.attributes.LatLonBBAttribute; import no.met.metadataeditor.dataTypes.attributes.LatLonBBSingleAttribute; import no.met.metadataeditor.dataTypes.attributes.ListElementAttribute; import no.met.metadataeditor.dataTypes.attributes.StartAndStopTimeAttribute; import no.met.metadataeditor.dataTypes.attributes.StringAndListElementAttribute; import no.met.metadataeditor.dataTypes.attributes.StringAttribute; import no.met.metadataeditor.dataTypes.attributes.TimeAttribute; import no.met.metadataeditor.dataTypes.attributes.UriAttribute; import no.met.metadataeditor.dataTypes.attributes.XMDDisplayArea; import no.met.metadataeditor.dataTypes.attributes.XMDInfoAttribute; import no.met.metadataeditor.dataTypes.attributes.XMDProjectionDataset; import no.met.metadataeditor.dataTypes.attributes.XMDProjectionFimex; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.ConstructorUtils; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.helpers.DefaultHandler; class TemplateHandler extends DefaultHandler { final String EDT = "http://www.met.no/schema/metadataeditor/editorDataTypes"; Deque<EditorVariable> edtElements; Deque<String> fullPathElements; Deque<String> finalPathElements; // without EDT-elements // xpath to find an EditorVariable-attribute Map<String, Deque<EditorVariable>> attributeXPath; private Map<String, EditorVariable> resultConfig; StringBuffer elementContent; private Map<String, String> namespacePrefixes = new HashMap<>(); // mapping between namespace names like 'gmd' => 'ns1'. Used to fix XPath expressions hardcoded // in the templates private Map<String, String> prefixMapping = new HashMap<>(); private static Map<String, Class<? extends DataAttribute>> supportedTags = new HashMap<>(); static { supportedTags.put("container", ContainerAttribute.class); supportedTags.put("lonLatBoundingBox", LatLonBBAttribute.class); supportedTags.put("lonLatBoundingBoxSingle", LatLonBBSingleAttribute.class); supportedTags.put("string", StringAttribute.class); supportedTags.put("uri", UriAttribute.class); supportedTags.put("list", ListElementAttribute.class); supportedTags.put("stringAndList", StringAndListElementAttribute.class); supportedTags.put("startAndStopTime", StartAndStopTimeAttribute.class); supportedTags.put("time", TimeAttribute.class); supportedTags.put("date", DateAttribute.class); supportedTags.put("keyValueList", KeyValueListAttribute.class); supportedTags.put("xmdInfo", XMDInfoAttribute.class); supportedTags.put("xmdDisplayArea", XMDDisplayArea.class); supportedTags.put("xmdProjectionDataset", XMDProjectionDataset.class); supportedTags.put("xmdProjectionFimex", XMDProjectionFimex.class); } @Override public void startDocument() throws SAXException { edtElements = new ArrayDeque<>(); fullPathElements = new ArrayDeque<>(); finalPathElements = new ArrayDeque<>(); resultConfig = null; attributeXPath = new HashMap<>(); elementContent = new StringBuffer(); } private String variableAddAttributes(EditorVariable ev, Attributes atts) throws SAXException { ev.setMaxOccurs(getMaxOccurs(atts)); ev.setMinOccurs(getMinOccurs(atts)); ev.setSelectionXPath(getXPath(atts)); String res = atts.getValue("resource"); if (res != null) { URI uri; try { uri = new URI(res); } catch (URISyntaxException e) { throw new SAXNotRecognizedException(e.toString()); } ev.addResource(EditorVariable.DEFAULT_RESOURCE, uri); } return atts.getValue("varName"); } private String getXPath(Attributes atts){ String xpath = atts.getValue("xpath"); if( xpath == null ){ return null; } for( Map.Entry<String, String> entry : prefixMapping.entrySet() ){ String oldNamespace = entry.getKey(); String newNamespace = entry.getValue(); xpath = xpath.replace(oldNamespace + ":", newNamespace + ":"); } return xpath; } private static int getMinOccurs(Attributes atts){ String minOccurs = atts.getValue("minOccurs"); if(null == minOccurs){ return 1; } if(0 == minOccurs.length()){ throw new InvalidTemplateException("minOccurs was an empty string by needs to be a number.", InvalidTemplateException.INVALID_ATTRIBUTE_VALUE); } int minValue; try { minValue = Integer.parseInt(minOccurs); } catch(NumberFormatException e){ throw new InvalidTemplateException(e.getMessage(), InvalidTemplateException.INVALID_ATTRIBUTE_VALUE); } if( minValue < 0 ){ throw new InvalidTemplateException("minOccurs needs to 0 or larger", InvalidTemplateException.INVALID_ATTRIBUTE_VALUE); } return minValue; } private static int getMaxOccurs(Attributes atts){ String maxOccurs = atts.getValue("maxOccurs"); if (maxOccurs == null) { return 1; }else if ("unbounded".equals(maxOccurs)) { return Integer.MAX_VALUE; } else { int maxValue; try { maxValue = Integer.parseInt(maxOccurs); } catch(NumberFormatException e){ throw new InvalidTemplateException(e.getMessage(), InvalidTemplateException.INVALID_ATTRIBUTE_VALUE); } if( maxValue < 1 ){ throw new InvalidTemplateException("maxOccurs needs to be larger than 0", InvalidTemplateException.INVALID_ATTRIBUTE_VALUE); } return maxValue; } } private void addStandardEDT(String nsUri, String lName, Attributes atts) throws SAXException { DataAttribute da = createInstance(lName); EditorVariable ev = new EditorVariable(da); String varName = variableAddAttributes(ev, atts); fullPathElements.addLast(getTemplateQName(nsUri, String.format("%s[@varName='%s']",lName, varName))); ev.setTemplateXPath(StringUtils.join(fullPathElements.iterator(), "/")); edtElements.getLast().addChild(varName, ev); edtElements.addLast(ev); for (String key : ev.getAttrsType().keySet()) { if (! attributeXPath.containsKey(key)) { attributeXPath.put(key, new ArrayDeque<EditorVariable>()); } attributeXPath.get(key).addLast(ev); } } private DataAttribute createInstance(String lName) throws InvalidTemplateException { if(!supportedTags.containsKey(lName)){ throw new InvalidTemplateException("The tag '" + lName + "' is not supported", InvalidTemplateException.INVALID_TAG); } Class<? extends DataAttribute> cls = supportedTags.get(lName); try { return ConstructorUtils.invokeConstructor(cls); } catch (NoSuchMethodException e) { String msg = "Missing empty constructor class: " + cls; Logger.getLogger(TemplateHandler.class.getName()).log(Level.SEVERE, msg); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } catch (IllegalAccessException e) { String msg = "No access to empty constructor for class: " + cls; Logger.getLogger(TemplateHandler.class.getName()).log(Level.SEVERE, msg); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } catch (InvocationTargetException e) { String msg = "Invocation problems for empty constructor for class: " + cls; Logger.getLogger(TemplateHandler.class.getName()).log(Level.SEVERE, msg); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } catch (InstantiationException e) { String msg = "Instansiation problems for empty constructor for class: " + cls; Logger.getLogger(TemplateHandler.class.getName()).log(Level.SEVERE, msg); throw new EditorException(msg, e, EditorException.GENERAL_ERROR_CODE); } } /** * check if one of the EditorVariable Attributes are placed in an attribute * @param atts */ private void searchEditorAttributes(Attributes atts) { Set<String> keys = new HashSet<>(attributeXPath.keySet()); for (String key : keys) { Pattern keyPattern = Pattern.compile("\\s*\\Q$"+key+"\\E\\s*"); for (int i = 0; i < atts.getLength(); ++i) { if (keyPattern.matcher(atts.getValue(i)).matches()) { String xPath = org.apache.commons.lang3.StringUtils.join(finalPathElements.iterator(), "/"); xPath += "/@"+getTemplateQName(atts.getURI(i), atts.getLocalName(i)); Deque<EditorVariable> evs = attributeXPath.get(key); EditorVariable ev = evs.removeLast(); ev.setAttrsXPath(key, xPath); if (evs.size() == 0) { attributeXPath.remove(key); } } } } } private void searchEditorAttributesInContent() { Set<String> keys = new HashSet<>(attributeXPath.keySet()); for (String key : keys) { Pattern keyPattern = Pattern.compile("\\s*\\Q$"+key+"\\E\\s*"); if (keyPattern.matcher(elementContent).matches()) { String xPath = org.apache.commons.lang3.StringUtils.join(finalPathElements.iterator(), "/"); xPath += "/text()"; Deque<EditorVariable> evs = attributeXPath.get(key); EditorVariable ev = evs.removeLast(); ev.setAttrsXPath(key, xPath); if (evs.size() == 0) { attributeXPath.remove(key); } } } } private String getTemplateQName(String nsUri, String lName) { if(nsUri == null || "".equals(nsUri)){ return lName; } String prefix = namespacePrefixes.get(nsUri); if (prefix == null) { prefix = "ns" + namespacePrefixes.size(); namespacePrefixes.put(nsUri, prefix); } return String.format("%s:%s", prefix, lName); } // generate a xpath identifier with name and attributes private String generateXPathIdentifier(String nsUri, String lName, String qName, Attributes atts) { String xpathPart = getTemplateQName(nsUri, lName); List<String> attrs = new ArrayList<>(); for (int i = 0; i < atts.getLength(); ++i) { // only use local attributes if ("".equals(atts.getURI(i)) || atts.getURI(i).equals(nsUri)) { // don't use local attributes containing EditorAttributes wildcards if (!atts.getValue(i).matches("\\s*\\$.*")) { attrs.add(String.format("@%s='%s'", getTemplateQName(atts.getURI(i), atts.getLocalName(i)), atts.getValue(i))); } } } if (attrs.size() > 0) { xpathPart = String.format("%s[%s]", xpathPart, StringUtils.join(attrs.iterator(), " and ")); } return xpathPart; } @Override public void startElement(String nsUri, String lName, String qName, Attributes atts) throws SAXException { if (EDT.equals(nsUri)) { if ("editorDataTypes".equals(lName)) { // start element, empty container assert(edtElements.size() == 0); EditorVariable ev = new EditorVariable(new ContainerAttribute()); edtElements.addLast(ev); fullPathElements.addLast(getTemplateQName(nsUri, lName)); } else { addStandardEDT(nsUri, lName, atts); } } else { fullPathElements.addLast(getTemplateQName(nsUri, lName)); finalPathElements.addLast(generateXPathIdentifier(nsUri, lName, qName, atts)); searchEditorAttributes(atts); String documentXPath = StringUtils.join(finalPathElements.iterator(), "/"); for (EditorVariable ev : edtElements) { if (ev.getDocumentXPath() == null) { ev.setDocumentXPath(documentXPath); } } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { elementContent.append(ch, start, length); } @Override public void endElement(String nsUri, String lName, String qName) { searchEditorAttributesInContent(); elementContent.setLength(0); // clear fullPathElements.removeLast(); if (EDT.equals(nsUri)) { if ("editorDataTypes".equals(lName)) { assert(edtElements.size() == 1); resultConfig = edtElements.getFirst().getChildren(); validateConfig(resultConfig, ""); } edtElements.removeLast(); } else { finalPathElements.removeLast(); } } public Map<String, EditorVariable> getResultConfig() { return resultConfig; } public Map<String, String> getNamespacePrefixes() { return namespacePrefixes; } public void setNamespacePrefixes(Map<String,String> namespacePrefixes){ this.namespacePrefixes.clear(); this.prefixMapping.clear(); for(Map.Entry<String, String> entry : namespacePrefixes.entrySet()){ String prefix = "ns" + this.namespacePrefixes.size(); this.namespacePrefixes.put(entry.getKey(), prefix); this.prefixMapping.put(entry.getValue(), prefix); } } public void validateConfig(Map<String, EditorVariable> config, String namespace){ for( Map.Entry<String, EditorVariable> entry : config.entrySet()){ EditorVariable ev = entry.getValue(); if( !ev.attrsXPathValid() ){ throw new InvalidTemplateException("One or $<varname> attributes are missing for variable: " + namespace + entry.getKey(), InvalidTemplateException.MISSING_ATTRIBUTES); } validateConfig(ev.getChildren(), entry.getKey() + "::"); } } public static Map<String,Class<? extends DataAttribute>> getSupportedTags(){ return supportedTags; } }