package io.lumify.palantir.mr.mappers; import io.lumify.core.exception.LumifyException; import io.lumify.core.model.properties.LumifyProperties; import io.lumify.core.security.LumifyVisibility; import io.lumify.core.util.LumifyLogger; import io.lumify.core.util.LumifyLoggerFactory; import io.lumify.palantir.model.PtPropertyAndValue; import io.lumify.palantir.model.PtPropertyType; import io.lumify.palantir.util.JGeometryWrapper; import io.lumify.web.clientapi.model.VisibilityJson; import org.apache.hadoop.io.LongWritable; import org.securegraph.Metadata; import org.securegraph.VertexBuilder; import org.securegraph.Visibility; import org.securegraph.type.GeoPoint; import org.securegraph.type.GeoShape; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.awt.geom.Point2D; import java.io.ByteArrayInputStream; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class PtPropertyAndValueMapper extends PalantirMapperBase<LongWritable, PtPropertyAndValue> { private static final LumifyLogger LOGGER = LumifyLoggerFactory.getLogger(PtPropertyAndValueMapper.class); public static final Pattern DURATION_PATTERN = Pattern.compile("(\\d+):(\\d\\d)"); private Visibility visibility; private VisibilityJson visibilityJson; private DocumentBuilder dBuilder; private static Pattern VALUE_BODY_PATTERN = Pattern.compile("^<VALUE>(.*)</VALUE>$", Pattern.DOTALL); private static Pattern UNPARSED_VALUE_BODY_PATTERN = Pattern.compile("^<UNPARSED_VALUE>(.*)</UNPARSED_VALUE>$", Pattern.DOTALL); @Override protected void setup(Context context) throws IOException, InterruptedException { super.setup(context); loadPropertyTypes(context); visibility = new LumifyVisibility("").getVisibility(); visibilityJson = new VisibilityJson(); try { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); dBuilder = dbFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new LumifyException("Could not create document builder", e); } } @Override protected void safeMap(LongWritable key, PtPropertyAndValue ptPropertyAndValue, Context context) throws Exception { context.setStatus(key.toString()); if (ptPropertyAndValue.isDeleted()) { return; } PtPropertyType propertyType = getPropertyType(ptPropertyAndValue.getType()); if (propertyType == null) { throw new LumifyException("Could not find property type: " + ptPropertyAndValue.getType()); } String objectVertexId = PtObjectMapper.getObjectVertexId(ptPropertyAndValue.getLinkObjectId()); String propertyKey = getPropertyKey(ptPropertyAndValue); Metadata propertyMetadata = createMetadata(ptPropertyAndValue); Value value = cleanUpValueString(propertyType, ptPropertyAndValue.getValue()); JGeometryWrapper gisData = ptPropertyAndValue.getGeometryGis(); if (value == null && gisData == null) { return; } VertexBuilder v = prepareVertex(objectVertexId, visibility); if (value != null) { addValueToVertexBuilder(v, propertyType, propertyKey, propertyMetadata, value); } if (gisData != null) { addGisDataToVertexBuilder(v, propertyType, propertyKey, propertyMetadata, gisData); } v.save(getAuthorizations()); } private Metadata createMetadata(PtPropertyAndValue ptPropertyAndValue) { Metadata propertyMetadata = new Metadata(); LumifyProperties.CREATED_BY.setMetadata(propertyMetadata, PtUserMapper.getUserVertexId(ptPropertyAndValue.getCreatedBy()), visibility); LumifyProperties.CREATE_DATE.setMetadata(propertyMetadata, new Date(ptPropertyAndValue.getTimeCreated()), visibility); LumifyProperties.MODIFIED_BY.setMetadata(propertyMetadata, PtUserMapper.getUserVertexId(ptPropertyAndValue.getLastModifiedBy()), visibility); LumifyProperties.MODIFIED_DATE.setMetadata(propertyMetadata, new Date(ptPropertyAndValue.getLastModified()), visibility); LumifyProperties.VISIBILITY_JSON.setMetadata(propertyMetadata, visibilityJson, visibility); return propertyMetadata; } private void addValueToVertexBuilder(VertexBuilder v, PtPropertyType propertyType, String propertyKey, Metadata propertyMetadata, Value value) { if (value instanceof StringValue) { String innerKey = null; if (propertyType.isGisEnabled()) { innerKey = PtPropertyType.VALUE_SUFFIX; } String propertyName = getPropertyName(propertyType.getUri(), innerKey); String valueString = ((StringValue) value).getValue(); Object valueObject; try { valueObject = toValue(propertyType, null, valueString); } catch (Exception ex) { LOGGER.error("Could not convert property value: %s (propertyType: %s)", value, propertyType.getConfigUri(), ex); valueObject = valueString; propertyName += PtPropertyType.ERROR_SUFFIX; } v.addPropertyValue(propertyKey, propertyName, valueObject, propertyMetadata, visibility); } else if (value instanceof MapValue) { MapValue values = (MapValue) value; for (Map.Entry<String, String> valueEntry : values.getValues().entrySet()) { String innerKey = valueEntry.getKey(); String propertyName = getPropertyName(propertyType.getUri(), innerKey); String valueString = valueEntry.getValue(); Object valueObject; try { valueObject = toValue(propertyType, innerKey, valueString); } catch (Exception ex) { LOGGER.error("Could not convert property value: %s (innerKey: %s, propertyType: %s): %s", value, innerKey, propertyType.getConfigUri(), ex.getMessage(), ex); valueObject = valueString; propertyName += PtPropertyType.ERROR_SUFFIX; } v.addPropertyValue(propertyKey, propertyName, valueObject, propertyMetadata, visibility); } } else { throw new RuntimeException("Unexpected value type: " + value.getClass().getName()); } } private void addGisDataToVertexBuilder(VertexBuilder v, PtPropertyType propertyType, String propertyKey, Metadata propertyMetadata, JGeometryWrapper gisData) { String propertyName = getBaseIri() + propertyType.getUri() + PtPropertyType.GIS_SUFFIX; GeoShape geoShape = toGeoShape(gisData); v.addPropertyValue(propertyKey, propertyName, geoShape, propertyMetadata, visibility); } private GeoShape toGeoShape(JGeometryWrapper gisData) { switch (gisData.getType()) { case POINT: Point2D pt = gisData.getJavaPoint(); return new GeoPoint(pt.getY(), pt.getX()); default: throw new RuntimeException("Unhandled Geo-shape: " + gisData.getType()); } } private Object toValue(PtPropertyType ptPropertyType, String innerKey, String value) { String propertyType; if (innerKey == null) { propertyType = ptPropertyType.getConfigTypeBase(); } else { propertyType = ptPropertyType.getConfigComponentType(innerKey); } if (propertyType == null) { throw new RuntimeException("Could not find property type"); } if (propertyType.equals("com.palantir.type.String")) { return value; } if (propertyType.equals("com.palantir.type.Enumeration")) { return value; } if (propertyType.equals("com.palantir.type.Number")) { return parseNumber(value); } if (propertyType.equals("com.palantir.type.Date")) { return parseDate(value); } if (propertyType.equals("com.palantir.type.Composite")) { throw new RuntimeException("Found composite property type without innerKey"); } throw new RuntimeException("Unhandled property type"); } private Object parseDate(String value) { // July 2, 2013 09:51:48 -04:00 try { return new SimpleDateFormat("MMMM d, yyyy HHmmss Z").parse(value.replaceAll(":", "")); } catch (ParseException ex1) { try { return new SimpleDateFormat("MMMM d, yyyy").parse(value); } catch (ParseException ex2) { throw new RuntimeException("Could not parse date", ex2); } } } private Object parseNumber(String value) { try { if (value.contains(":")) { Matcher m = DURATION_PATTERN.matcher(value); if (m.matches()) { String minutePart = m.group(1); String secondPart = m.group(2); return ((Integer.parseInt(minutePart) * 60) + Integer.parseInt(secondPart)); } else { throw new RuntimeException("Number has a ':' but does not match duration pattern"); } } if (value.contains(".")) { return Double.parseDouble(value); } return Long.parseLong(value); } catch (Exception ex) { throw new RuntimeException("Could not parse number", ex); } } private String getPropertyName(String uri, String innerKey) { return getBaseIri() + uri + (innerKey == null ? "" : ("/" + innerKey)); } private Value cleanUpValueString(PtPropertyType propertyType, String value) throws IOException, SAXException { if (value == null) { return null; } value = value.trim(); if (value.equals("<null></null>")) { return null; } Matcher m = VALUE_BODY_PATTERN.matcher(value); if (m.matches()) { value = m.group(1).trim(); } m = VALUE_BODY_PATTERN.matcher(value); if (m.matches()) { return new StringValue(m.group(1).trim()); } m = UNPARSED_VALUE_BODY_PATTERN.matcher(value); if (m.matches()) { return new StringValue(m.group(1).trim()); } Document d = dBuilder.parse(new ByteArrayInputStream(("<v>" + value + "</v>").getBytes())); Map<String, String> values = new HashMap<>(); NodeList childNodes = d.getDocumentElement().getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node childNode = childNodes.item(i); if (childNode instanceof Element) { Element e = (Element) childNode; String tagName = e.getTagName(); values.put(tagName, e.getTextContent()); } } return new MapValue(values); } private String getPropertyKey(PtPropertyAndValue ptPropertyAndValue) { return ID_PREFIX + ptPropertyAndValue.getPropertyValueId(); } private static abstract class Value { } private static class StringValue extends Value { private final String value; public StringValue(String value) { this.value = value; } public String getValue() { return value; } @Override public String toString() { return "StringValue{" + "value='" + value + '\'' + '}'; } } private static class MapValue extends Value { private final Map<String, String> values; public MapValue(Map<String, String> values) { this.values = values; } public Map<String, String> getValues() { return values; } @Override public String toString() { return "MapValue{" + "values=" + values + '}'; } } }