package org.constellation.json.metadata.v2; import java.lang.reflect.Method; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.Objects; import java.util.Set; import org.apache.sis.measure.Angle; import org.apache.sis.metadata.AbstractMetadata; import org.apache.sis.metadata.MetadataStandard; import org.apache.sis.util.CharSequences; import org.apache.sis.util.iso.Types; import static org.constellation.json.JsonMetadataConstants.DATE_FORMAT; import static org.constellation.json.JsonMetadataConstants.DATE_HOUR_FORMAT; import static org.constellation.json.JsonMetadataConstants.DATE_READ_ONLY; import org.constellation.json.metadata.ParseException; import org.constellation.json.metadata.binding.RootObj; import org.constellation.util.ReflectionUtilities; import org.opengis.temporal.Instant; import org.opengis.temporal.Period; import org.opengis.util.ControlledVocabulary; /** * Metadata Object ===> RootObj * * @author guilhem */ public class TemplateWriter extends AbstractTemplateHandler { public TemplateWriter(final MetadataStandard standard) { super(standard); } /** * Write a metadata Object into a template * * @param template * @param metadata * @param prune * @param overwrite * @return * @throws org.constellation.json.metadata.ParseException */ public RootObj writeTemplate(final RootObj template, final Object metadata, final boolean prune, final boolean overwrite) throws ParseException { final TemplateTree tree = TemplateTree.getTreeFromRootObj(template); fillValueWithMetadata(tree, tree.getRoot(), metadata, new HashMap<String, Set<Object>>(), prune, overwrite); if (prune) { TemplateTree.pruneTree(tree, tree.getRoot()); } return TemplateTree.getRootObjFromTree(template, tree, prune); } private void fillValueWithMetadata(final TemplateTree tree, final ValueNode root, final Object metadata, final Map<String, Set<Object>> excluded, final boolean prune, final boolean overwrite) throws ParseException { final List<ValueNode> children = new ArrayList<>(root.children); for (ValueNode node : children) { final ValueNode origNode = new ValueNode(node); final Object obj = getValue(node, metadata, excluded, overwrite); if (obj instanceof Collection && !((Collection)obj).isEmpty()) { final Iterator it = ((Collection)obj).iterator(); int i = node.ordinal; while (it.hasNext()) { Object child = it.next(); node = tree.duplicateNode(origNode, i); if (node.isField()) { node.value = valueToString(node, child, !prune, overwrite); } else { fillValueWithMetadata(tree, node, child, excluded, prune, overwrite); } i++; } } else { if (node.isField()) { node.value = valueToString(node, obj, !prune, overwrite); } else { fillValueWithMetadata(tree, node, obj, excluded, prune, overwrite); } } } } private ValueNode extractSubTreeFromMetadata(final ValueNode root, final Object metadata, final Map<String, Set<Object>> excluded) throws ParseException { if (root.isField()) { root.value = valueToString(root, metadata, false, false); return root; } final List<ValueNode> children = new ArrayList<>(root.children); for (ValueNode node : children) { final ValueNode origNode = new ValueNode(node); final Object obj = getValue(node, metadata, excluded, false); if (obj instanceof Collection && !((Collection)obj).isEmpty()) { final Iterator it = ((Collection)obj).iterator(); int i = node.ordinal; boolean first = true; while (it.hasNext()) { Object child = it.next(); if (!first) { node = new ValueNode(origNode, root, i); } if (node.isField()) { node.value = valueToString(node, child, false, false); } else { extractSubTreeFromMetadata(node, child, excluded); } first = false; i++; } } else { if (node.isField()) { node.value = valueToString(node, obj, false, false); } else { extractSubTreeFromMetadata(node, obj, excluded); } } } return root; } private Object getValue(final ValueNode node, Object metadata, Map<String, Set<Object>> excluded, boolean overwrite) throws ParseException { if (metadata instanceof AbstractMetadata && !(metadata instanceof Period) && !(metadata instanceof Instant)) { Object obj = asFullMap(metadata).get(node.name); if (obj instanceof Collection) { final Collection result = new ArrayList<>(); final Iterator it = ((Collection)obj).iterator(); while (it.hasNext()) { final Object o = getSingleValue(node, it.next(), excluded, overwrite); if (o != null) result.add(o); } return result; } else { return getSingleValue(node, obj, excluded, overwrite); } } else if (metadata instanceof Collection && ((Collection)metadata).isEmpty()) { return null; } else if (metadata != null) { // TODO filter : type, default value, etc... final Method getter = ReflectionUtilities.getGetterFromName(node.name, metadata.getClass()); if (getter != null) { return ReflectionUtilities.invokeMethod(metadata, getter); } LOGGER.warning("Unable to find:" + metadata.getClass().getName() + " getter for:" + node.name); return null; } else { return null; } } private Object getSingleValue(final ValueNode node, Object metadata, Map<String, Set<Object>> excluded, boolean overwrite) throws ParseException { if (isExcluded(excluded, node, metadata)) return null; /* * In strict mode, we want that the sub-tree of the object correspound exactly the node tree. * For a collection, we return a sub-collection with only the matching instance * * The matching point are read-only fields and types. */ if (node.strict) { if (node.type != null) { final Class type = readType(node); if (!type.isInstance(metadata)) { return null; } } final ValueNode candidate = extractSubTreeFromMetadata(new ValueNode(node), metadata, new HashMap<String, Set<Object>>()); if (matchNode(node, candidate)) { exclude(excluded, node, metadata); return metadata; } return null; /* * if the node has a type we verify that the values correspound to the declared type. * For a collection, we return a sub-collection with only the matching instance */ } else if (node.type != null) { final Class type = readType(node); if (type.isInstance(metadata) || overwrite) { exclude(excluded, node, metadata); return metadata; } return null; /* * else return simply the object */ } else { // exclude ?? return metadata; } } private static boolean matchNode(final ValueNode origin, final ValueNode candidate) { if (Objects.equals(origin.type, candidate.type)) { if (origin.render != null && origin.render.contains("readonly") && !Objects.equals(origin.defaultValue, candidate.value)) { return false; } else if (!origin.getPredefinedValues().isEmpty() && !origin.getPredefinedValues().contains(candidate.value)) { return false; } for (ValueNode originChild : origin.children) { final List<ValueNode> candidateChildren = candidate.getChildrenByName(originChild.name); if (!originChild.multiple && candidateChildren.size() > 1) { return false; } for (ValueNode candidateChild : candidateChildren) { if (!matchNode(originChild, candidateChild)) { return false; } } } return true; } return false; } private static String valueToString(final ValueNode n, final Object value, final boolean applyDefault, final boolean overwrite) { final String p; // Null or empty collection if (value == null || value instanceof Collection) { if (applyDefault || overwrite) { p = n.defaultValue; } else { p = null; } } else if (overwrite && n.defaultValue != null && n.render.toLowerCase().contains("readonly")) { p = n.defaultValue; } else if (value instanceof Number) { p = value.toString(); } else if (value instanceof Angle) { p = Double.toString(((Angle) value).degrees()); } else { /* * Above were unquoted cases. Below are texts to quote. */ if (value instanceof ControlledVocabulary) { p = Types.getStandardName(value.getClass()) + '.' + Types.getCodeName((ControlledVocabulary) value); } else if (value instanceof Date) { if (DATE_READ_ONLY.equals(n.render)) { synchronized (DATE_HOUR_FORMAT) { String dateTime = DATE_HOUR_FORMAT.format(value); // remove uneccesary time if (dateTime.endsWith(" 00:00:00")) { p = dateTime.substring(0, dateTime.lastIndexOf(" 00:00:00")); } else { p = dateTime; } } } else { synchronized (DATE_FORMAT) { p = DATE_FORMAT.format(value); } } } else if (value instanceof Locale) { String language; try { language = ((Locale) value).getISO3Language(); } catch (MissingResourceException e) { language = ((Locale) value).getLanguage(); } p = "LanguageCode." + language; } else if (value instanceof Charset) { p = ((Charset) value).name(); } else { CharSequence cs = value.toString(); cs = CharSequences.replace(cs, "\"", "\\\""); cs = CharSequences.replace(cs, "\t", "\\t"); cs = CharSequences.replace(cs, "\n", "\\n"); p = cs.toString(); } } return p; } private static void exclude(final Map<String, Set<Object>> excluded, final ValueNode node, final Object obj) { if (excluded.containsKey(node.path)) { excluded.get(node.path).add(obj); } else { final HashSet<Object> set = new HashSet<>(); set.add(obj); excluded.put(node.path, set); } } private static boolean isExcluded(final Map<String, Set<Object>> excluded, final ValueNode node, final Object obj) { return excluded.containsKey(node.path) && excluded.get(node.path).contains(obj); } private static class NumeratedCollectionElement { public int index; public Object obj; public NumeratedCollectionElement(int index, Object obj) { this.index = index; this.obj = obj; } } }