/* * #%L * ===================================================== * _____ _ ____ _ _ _ _ * |_ _|_ __ _ _ ___| |_ / __ \| | | | ___ | | | | * | | | '__| | | / __| __|/ / _` | |_| |/ __|| |_| | * | | | | | |_| \__ \ |_| | (_| | _ |\__ \| _ | * |_| |_| \__,_|___/\__|\ \__,_|_| |_||___/|_| |_| * \____/ * * ===================================================== * * Hochschule Hannover * (University of Applied Sciences and Arts, Hannover) * Faculty IV, Dept. of Computer Science * Ricklinger Stadtweg 118, 30459 Hannover, Germany * * Email: trust@f4-i.fh-hannover.de * Website: http://trust.f4.hs-hannover.de/ * * This file is part of visitmeta-visualization, version 0.6.0, * implemented by the Trust@HsH research group at the Hochschule Hannover. * %% * Copyright (C) 2012 - 2016 Trust@HsH * %% * 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. * #L% */ package de.hshannover.f4.trust.visitmeta.network; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import javax.ws.rs.core.MediaType; import javax.xml.bind.DatatypeConverter; import org.apache.log4j.Logger; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonProcessingException; import org.codehaus.jackson.map.ObjectMapper; import com.sun.jersey.api.client.WebResource; import de.hshannover.f4.trust.visitmeta.implementations.IdentifierGraphImpl; import de.hshannover.f4.trust.visitmeta.implementations.IdentifierImpl; import de.hshannover.f4.trust.visitmeta.implementations.LinkImpl; import de.hshannover.f4.trust.visitmeta.implementations.MetadataImpl; import de.hshannover.f4.trust.visitmeta.implementations.internal.DeltaImpl; import de.hshannover.f4.trust.visitmeta.interfaces.Delta; import de.hshannover.f4.trust.visitmeta.interfaces.GraphFilter; import de.hshannover.f4.trust.visitmeta.interfaces.GraphService; import de.hshannover.f4.trust.visitmeta.interfaces.GraphType; import de.hshannover.f4.trust.visitmeta.interfaces.Identifier; import de.hshannover.f4.trust.visitmeta.interfaces.IdentifierGraph; import de.hshannover.f4.trust.visitmeta.interfaces.Metadata; /** * * @author Ralf Steuerwald * @author Bastian Hellmann * */ public class ProxyGraphService implements GraphService { private static final Logger log = Logger.getLogger(ProxyGraphService.class); private WebResource mService; private boolean mIncludeRawXML; private ObjectMapper mObjectMapper = new ObjectMapper(); private static final String TIMESTAMP = "timestamp"; private static final String LINKS = "links"; private static final String IDENTIFIERS = "identifiers"; private static final String TYPENAME = "typename"; private static final String PROPERTIES = "properties"; private static final String METADATA = "metadata"; private static final String DELTA_UPDATES = "updates"; private static final String DELTA_DELETES = "deletes"; private static final String RAW_XML = "rawData"; public ProxyGraphService(WebResource service) { mService = service; mIncludeRawXML = false; } public ProxyGraphService(WebResource service, boolean includeRawXML) { mService = service; mIncludeRawXML = includeRawXML; } /** * @throws RuntimeException * @throws {@link NumberFormatException} */ @Override public SortedMap<Long, Long> getChangesMap() { log.trace("Method getChangesMap() called."); String json = mService .path("changes") .queryParam("rawData", Boolean.toString(mIncludeRawXML)) .accept(MediaType.APPLICATION_JSON) .get(String.class); SortedMap<Long, Long> changesMap = new TreeMap<>(); JsonNode node = parseJson(json); Iterator<String> keys = node.getFieldNames(); while (keys.hasNext()) { String key = keys.next(); JsonNode valueNode = node.get(key); if (valueNode.isInt()) { long timestamp = Long.parseLong(key); long changes = valueNode.getIntValue(); changesMap.put(timestamp, changes); } else { throw new RuntimeException("value of '"+key+"' is not an int"); } } return changesMap; } @Override public List<IdentifierGraph> getInitialGraph() { log.trace("Method getInitialGraph() called."); String json = mService .path("initial") .queryParam("rawData", Boolean.toString(mIncludeRawXML)) .accept(MediaType.APPLICATION_JSON) .get(String.class); JsonNode rootNode = parseJson(json); return extractGraphsFromJson(rootNode); } @Override public List<IdentifierGraph> getGraphAt(long timestamp) { log.trace("Method getGraphAt(" + timestamp + ") called."); String json = mService .path(Long.toString(timestamp)) .queryParam("rawData", Boolean.toString(mIncludeRawXML)) .accept(MediaType.APPLICATION_JSON) .get(String.class); JsonNode rootNode = parseJson(json); return extractGraphsFromJson(rootNode); } @Override public List<IdentifierGraph> getNotifiesAt(long timestamp) { //TODO not yet implemented log.info("not yet implemented!"); return null; } @Override public List<IdentifierGraph> getCurrentGraph() { log.trace("Method getCurrentGraph() called."); String json = mService .path("current") .queryParam("rawData", Boolean.toString(mIncludeRawXML)) .accept(MediaType.APPLICATION_JSON) .get(String.class); JsonNode rootNode = parseJson(json); return extractGraphsFromJson(rootNode); } @Override public List<IdentifierGraph> getInitialGraph(GraphFilter filter) { log.trace("Method getInitialGraph(" + filter + ") called."); throw new UnsupportedOperationException(); } @Override public List<IdentifierGraph> getGraphAt(long timestamp, GraphFilter filter) { log.trace("Method getGraphAt(" + timestamp + ", " + filter + ") called."); throw new UnsupportedOperationException(); } @Override public List<IdentifierGraph> getCurrentGraph(GraphFilter filter) { log.trace("Method getCurrentGraph(" + filter + ") called."); throw new UnsupportedOperationException(); } @Override public Delta getDelta(long t1, long t2) { log.trace("Method getDelta(" + t1 + "," + t2 + ") called."); String json = mService .path(Long.toString(t1) + "/" + Long.toString(t2)) .queryParam("rawData", Boolean.toString(mIncludeRawXML)) .accept(MediaType.APPLICATION_JSON) .get(String.class); return extractDeltasFromJson(json); } private Delta extractDeltasFromJson(String json) { List<IdentifierGraph> updates = new ArrayList<IdentifierGraph>(); List<IdentifierGraph> deletes = new ArrayList<IdentifierGraph>(); // TODO fill lists /** * {"updates":[],"deletes":[]} * multiple timestamps */ JsonNode rootNode = parseJson(json); JsonNode jsonDeltaUpdates = rootNode.get(DELTA_UPDATES); if (jsonDeltaUpdates != null) { log.trace("Found updates on delta"); if (jsonDeltaUpdates.isArray()) { Iterator<JsonNode> updateElements = jsonDeltaUpdates.getElements(); while (updateElements.hasNext()) { updates.add(buildGraphFromJson(updateElements.next())); } } } JsonNode jsonDeltaDeletes = rootNode.get(DELTA_DELETES); if (jsonDeltaUpdates != null) { log.trace("Found deletes on delta"); if (jsonDeltaDeletes.isArray()) { Iterator<JsonNode> deleteElements = jsonDeltaDeletes.getElements(); while (deleteElements.hasNext()) { deletes.add(buildGraphFromJson(deleteElements.next())); } } } return new DeltaImpl(deletes, updates); } private IdentifierGraph buildGraphFromJson(JsonNode jsonGraph) { log.trace("Method buildGraphFromJson(...) called."); log.trace("Parameter 'jsonGraph': " + jsonGraph); JsonNode jsonTimestamp = jsonGraph.get(TIMESTAMP); if (jsonTimestamp == null) { throw new RuntimeException("timestamp for graph is missing in '"+jsonGraph+"'"); } if (!jsonTimestamp.isLong()) { throw new RuntimeException("timestamp is not a long in '"+jsonGraph+"'"); } long timestamp = jsonTimestamp.getLongValue(); IdentifierGraphImpl graph = new IdentifierGraphImpl(timestamp); JsonNode jsonlinkList = jsonGraph.get(LINKS); if (jsonlinkList == null) { throw new RuntimeException("no links in graph '"+jsonGraph+"' found"); } insertIdentifierInto(graph, jsonlinkList); return graph; } private void insertIdentifierInto(IdentifierGraphImpl graph, JsonNode jsonLinkList) { log.trace("Method insertIdentifierInto(...) called."); Iterator<JsonNode> jsonLinks = jsonLinkList.getElements(); while (jsonLinks.hasNext()) { JsonNode jsonLink = jsonLinks.next(); JsonNode identifiers = jsonLink.get(IDENTIFIERS); JsonNode metadata = jsonLink.get(METADATA); // two identifiers -> link if (identifiers.isArray()) { log.trace("JsonNode contains two identifiers"); Iterator<JsonNode> ids = identifiers.getElements(); List<IdentifierImpl> identifierList = new ArrayList<IdentifierImpl>(); while (ids.hasNext()) { JsonNode currentIdentifier = ids.next(); IdentifierImpl identifier = (IdentifierImpl) buildIdentifierFromJson(currentIdentifier); identifierList.add(identifier); } for (IdentifierImpl identifier : identifierList) { IdentifierImpl identifierFound = graph.findIdentifier(identifier); if (identifierFound == null) { log.trace("Identifier not found in graph, inserting: " + identifier); graph.insert(identifier); } else { identifierList.set(identifierList.indexOf(identifier), identifierFound); } } LinkImpl linkImpl = graph.connect(identifierList.get(0), identifierList.get(1)); log.trace("Adding metadata to link'" + linkImpl + "'"); List<Metadata> metadataList = extractMetadata(metadata); for (Metadata m : metadataList) { linkImpl.addMetadata(m); } log.trace("Creating link: " + linkImpl); // one identifier } else { log.trace("JsonNode contains one identifier"); // convert Identifier to IdentifierImpl IdentifierImpl identifier = (IdentifierImpl) buildIdentifierFromJson(identifiers); // FIXME InternalIdentifier on the client side? bah?! // TODO <VA> Who wrote the above FIXME comment and what shall it mean? Is it already fixed? // insert identifier into graph IdentifierImpl identifierFound = graph.findIdentifier(identifier); if (identifierFound == null) { log.trace("Identifier not found, inserting: " + identifier); graph.insert(identifier); } else { identifier = identifierFound; } log.trace("Adding metadata to identifier '" + identifier + "'"); List<Metadata> metadataList = extractMetadata(metadata); for (Metadata m : metadataList) { identifier.addMetadata(m); } } } } private List<Metadata> extractMetadata(JsonNode jsonNode) { log.trace("Method extractMetadata(..) called"); log.trace("Parameter 'jsonNode':" + jsonNode); if (jsonNode == null) { log.trace("No metadata found."); return new ArrayList<Metadata>(); } Metadata metadatum = null; List<Metadata> metadataList = new ArrayList<Metadata>(); // multiple metadata if (jsonNode.isArray()) { Iterator<JsonNode> metadatas = jsonNode.getElements(); JsonNode currentMetadata = null; while (metadatas.hasNext()) { currentMetadata = metadatas.next(); metadatum = buildMetadataFromJson(currentMetadata); metadataList.add(metadatum); } // only one metadatum } else { metadatum = buildMetadataFromJson(jsonNode); metadataList.add(metadatum); } return metadataList; } private Metadata buildMetadataFromJson(JsonNode jsonMetadata) { log.trace("Method buildIdentifierFromJson(...) called."); log.trace("Parameter 'jsonIdentifier': " + jsonMetadata); JsonNode jsonTypename = jsonMetadata.get(TYPENAME); if (jsonTypename == null) { throw new RuntimeException("no typename found for metadata '"+jsonMetadata+"'"); } String typename = jsonTypename.getValueAsText(); JsonNode jsonProperties = jsonMetadata.get(PROPERTIES); if (jsonProperties == null) { throw new RuntimeException("no properties found for metadata '"+jsonMetadata+"'"); } boolean isSingleValue = false; long publishTimestamp = 0; Map<String, String> propertiesMap = new HashMap<String, String>(); Iterator<String> properties = jsonProperties.getFieldNames(); while (properties.hasNext()) { String key = properties.next(); String value = jsonProperties.get(key).getValueAsText(); if (key.contains("@ifmap-cardinality")) { isSingleValue = value.equalsIgnoreCase("singleValue") ? true : false; } else if (key.contains("[@ifmap-timestamp]")) { publishTimestamp = getXsdStringAsCalendar(value); } propertiesMap.put(key, value); } log.trace("Creating metadata (typename: " + typename + ", isSingleValue: " + isSingleValue + ", publishTimestamp: " + publishTimestamp + ")"); MetadataImpl metadata = new MetadataImpl(typename, isSingleValue, publishTimestamp); for (String key : propertiesMap.keySet()) { log.trace("Adding property to metadata: " + key + " = " + propertiesMap.get(key)); metadata.addProperty(key, propertiesMap.get(key)); } if (mIncludeRawXML) { JsonNode jsonRawXML = jsonMetadata.get(RAW_XML); if (jsonRawXML != null ) { metadata.setRawData(jsonRawXML.getValueAsText()); } } return metadata; } private Identifier buildIdentifierFromJson(JsonNode jsonIdentifier) { log.trace("Method buildIdentifierFromJson(...) called."); log.trace("Parameter 'jsonIdentifier': " + jsonIdentifier); JsonNode jsonTypename = jsonIdentifier.get(TYPENAME); if (jsonTypename == null) { throw new RuntimeException("no typename found for identifier '"+jsonIdentifier+"'"); } String typename = jsonTypename.getValueAsText(); IdentifierImpl identifier = new IdentifierImpl(typename); JsonNode jsonProperties = jsonIdentifier.get(PROPERTIES); if (jsonProperties == null) { throw new RuntimeException("no properties found for identifier '"+jsonIdentifier+"'"); } Iterator<String> properties = jsonProperties.getFieldNames(); while (properties.hasNext()) { String key = properties.next(); String value = jsonProperties.get(key).getValueAsText(); identifier.addProperty(key, value); } if (mIncludeRawXML) { JsonNode jsonRawXML = jsonIdentifier.get(RAW_XML); if (jsonRawXML != null ) { identifier.setRawData(jsonRawXML.getValueAsText()); } } return identifier; } /** * @throws RuntimeException */ private JsonNode parseJson(String json) { log.trace("Method parseJson(...) called."); log.trace("Parameter 'json': " + json); try { // TODO <VA>: debug only, remove later? ObjectMapper mapper = new ObjectMapper(); Object jsonObject = mapper.readValue(json, Object.class); String jsonFormatted = mapper.defaultPrettyPrintingWriter().writeValueAsString(jsonObject); log.debug("Parameter 'json' (formatted) in parseJson():\n" + jsonFormatted); return mObjectMapper.readTree(json); } catch (JsonProcessingException e) { throw new RuntimeException("could not parse '"+json+"' as JSON: " + e.getMessage(), e); } catch (IOException e) { throw new RuntimeException("error while reading '"+json+"' as JSON: " + e.getMessage(), e); } } /** * @param json * @return */ private List<IdentifierGraph> extractGraphsFromJson(JsonNode rootNode) { List<IdentifierGraph> graphs = new ArrayList<IdentifierGraph>(); Iterator<JsonNode> jsonGraphs = rootNode.getElements(); while (jsonGraphs.hasNext()) { JsonNode currentJsonGraph = jsonGraphs.next(); IdentifierGraph graph = buildGraphFromJson(currentJsonGraph); graphs.add(graph); } return graphs; } /** * Transforms a given xsd:DateTime {@link String} (e.g. 2003-05-31T13:20:05-05:00) to a Java {@link Calendar} object. Uses DatetypeConverter to parse the * {@link String} object. * * @param xsdDateTime - the xsd:DateTime that is to be transformed. * @return a {@link Calendar} object representing the given xsd:DateTime */ private Long getXsdStringAsCalendar(String xsdDateTime) { assert xsdDateTime != null && !xsdDateTime.isEmpty(); try { if (xsdDateTime.contains("+")) { int idxTz = xsdDateTime.lastIndexOf("+"); int idxLastColon = xsdDateTime.lastIndexOf(":"); if(idxLastColon < idxTz) { String p1 = xsdDateTime.substring(0, idxTz+3); String p2 = xsdDateTime.substring(idxTz+3, xsdDateTime.length()); xsdDateTime = p1 + ":" + p2; } } if (xsdDateTime.contains(":")) { // if the String contains an ':' literal, we try to interpret it as a xsdDateTime-string return DatatypeConverter.parseDateTime(xsdDateTime).getTimeInMillis(); } else { // try to parse a time in milliseconds to a Calendar object Calendar tmp = new GregorianCalendar(); tmp.setTimeInMillis(Long.parseLong(xsdDateTime)); return tmp.getTimeInMillis(); } } catch (IllegalArgumentException e) { log.error("Illegal data/time format found (incoming String was: " + xsdDateTime + "); setting to current date/time."); return new GregorianCalendar().getTimeInMillis(); } } @Override public long count(GraphType type) { log.error("Not implemented!"); throw new RuntimeException("Not implemented!"); } @Override public long count(GraphType type, long timestamp) { log.error("Not implemented!"); throw new RuntimeException("Not implemented!"); } @Override public long count(GraphType type, long from, long to) { log.error("Not implemented!"); throw new RuntimeException("Not implemented!"); } @Override public double meanOfEdges() { log.error("Not implemented!"); throw new RuntimeException("Not implemented!"); } @Override public double meanOfEdges(long timestamp) { log.error("Not implemented!"); throw new RuntimeException("Not implemented!"); } }