| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you 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. |
| */ |
| package org.apache.clerezza.rdf.rdfjson.serializer; |
| |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import org.apache.clerezza.commons.rdf.BlankNode; |
| import org.apache.clerezza.commons.rdf.BlankNodeOrIRI; |
| import org.apache.clerezza.commons.rdf.RDFTerm; |
| import org.apache.clerezza.commons.rdf.Triple; |
| import org.apache.clerezza.commons.rdf.Graph; |
| import org.apache.clerezza.commons.rdf.IRI; |
| import org.apache.clerezza.commons.rdf.Literal; |
| import org.apache.clerezza.rdf.core.serializedform.SerializingProvider; |
| import org.apache.clerezza.rdf.core.serializedform.SupportedFormat; |
| import org.apache.felix.scr.annotations.Component; |
| import org.apache.felix.scr.annotations.Service; |
| import org.json.simple.JSONArray; |
| import org.json.simple.JSONObject; |
| |
| /** |
| * A {@link org.apache.clerezza.rdf.core.serializedform.SerializingProvider} for |
| * rdf/json. |
| * |
| * This implementation is based on first sorting the triples within the parsed |
| * {@link Graph} based on the {@link #SUBJECT_COMPARATOR subject}. |
| * <p> |
| * The serialization is done on a subject scope. Meaning that all triples for a |
| * subject are serialized and instantly written to the provided |
| * {@link OutputStream}. |
| * <p> |
| * 'UFT-8' is used as encoding to write the data. |
| * |
| * @author tio, hasan, rwesten |
| */ |
| @Component(immediate = true) |
| @Service(SerializingProvider.class) |
| @SupportedFormat(SupportedFormat.RDF_JSON) |
| public class RdfJsonSerializingProvider implements SerializingProvider { |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public void serialize(OutputStream serializedGraph, Graph tc, |
| String formatIdentifier) { |
| if (tc.isEmpty()) { // ensure writing an empty element in case of an |
| // empty collection |
| try { |
| serializedGraph.write(new JSONObject().toJSONString().getBytes( |
| "UTF-8")); |
| } catch (IOException e) { |
| throw new IllegalStateException( |
| "Exception while writing to parsed OutputStream", e); |
| } |
| return; |
| } |
| BlankNodeManager bNodeMgr = new BlankNodeManager(); |
| BufferedWriter out; |
| try { |
| out = new BufferedWriter(new OutputStreamWriter(serializedGraph, |
| "UTF-8")); |
| } catch (UnsupportedEncodingException e) { |
| throw new IllegalStateException( |
| "Encoding 'UTF-8' is not supported by this System", e); |
| } |
| Triple[] sortedTriples = tc.toArray(new Triple[tc.size()]); |
| Arrays.sort(sortedTriples, SUBJECT_COMPARATOR); |
| Triple triple; |
| BlankNodeOrIRI subject = null; |
| String subjectStr = null; |
| IRI predicate = null; |
| Map<IRI, JSONArray> predicateValues = new HashMap<IRI, JSONArray>(); |
| JSONObject jSubject = new JSONObject(); |
| try { |
| out.write("{"); // start the root object |
| for (int i = 0; i < sortedTriples.length; i++) { |
| triple = sortedTriples[i]; |
| boolean subjectChange = !triple.getSubject().equals(subject); |
| if (subjectChange) { |
| if (subject != null) { |
| // write the predicate values |
| for (Entry<IRI, JSONArray> predicates : predicateValues |
| .entrySet()) { |
| jSubject.put( |
| predicates.getKey().getUnicodeString(), |
| predicates.getValue()); |
| } |
| // write subject |
| out.write(JSONObject.toString(subjectStr, jSubject)); |
| out.write(","); |
| jSubject.clear(); // just clear |
| predicateValues.clear(); |
| } |
| // init next subject |
| subject = triple.getSubject(); |
| if (subject instanceof BlankNode) { |
| subjectStr = bNodeMgr.getBlankNodeId((BlankNode) subject); |
| } else { // if (subject instanceof IRI) |
| subjectStr = ((IRI) subject).getUnicodeString(); |
| } |
| } |
| predicate = triple.getPredicate(); |
| JSONArray values = predicateValues.get(predicate); |
| if (values == null) { |
| values = new JSONArray(); |
| predicateValues.put(predicate, values); |
| } |
| values.add(writeObject(bNodeMgr, triple.getObject())); |
| } |
| if (subjectStr != null) { |
| for (Entry<IRI, JSONArray> predicates : predicateValues |
| .entrySet()) { |
| jSubject.put(predicates.getKey().getUnicodeString(), |
| predicates.getValue()); |
| } |
| out.write(JSONObject.toString(subjectStr, jSubject)); |
| } |
| out.write("}");// end the root object |
| out.flush(); |
| } catch (IOException e) { |
| throw new IllegalStateException( |
| "Exception while writing on the parsed OutputStream", e); |
| } |
| } |
| |
| private class BlankNodeManager { |
| private Map<BlankNode, String> bNodeMap = new HashMap<BlankNode, String>(); |
| private int counter = 0; |
| |
| public String getBlankNodeId(BlankNode node) { |
| String bNodeId = bNodeMap.get(node); |
| if (bNodeId == null) { |
| bNodeId = "_:b" + ++counter; |
| bNodeMap.put((BlankNode) node, bNodeId); |
| } |
| return bNodeId; |
| } |
| } |
| |
| /** |
| * Converts the {@link RDFTerm object} of an triple to JSON |
| * |
| * @param bNodeMgr |
| * used to lookup {@link BlankNode} instances |
| * @param object |
| * the object of the triple |
| * @return the JSON representation of parsed object |
| */ |
| @SuppressWarnings("unchecked") |
| private JSONObject writeObject(BlankNodeManager bNodeMgr, RDFTerm object) { |
| JSONObject jObject = new JSONObject(); |
| if (object instanceof Literal) { |
| Literal literal = (Literal) object; |
| jObject.put("value", literal.getLexicalForm()); |
| jObject.put("type", "literal"); |
| jObject.put("datatype", literal.getDataType().getUnicodeString()); |
| if (literal.getLanguage() != null) { |
| jObject.put("lang", literal.getLanguage().toString()); |
| } |
| } else if (object instanceof IRI) { |
| IRI uriRef = (IRI) object; |
| jObject.put("value", uriRef.getUnicodeString()); |
| jObject.put("type", "uri"); |
| } else if (object instanceof BlankNode) { |
| String bNodeId = bNodeMgr.getBlankNodeId((BlankNode) object); |
| jObject.put("value", bNodeId); |
| jObject.put("type", "bnode"); |
| } |
| return jObject; |
| } |
| |
| /** |
| * Compares only the subjects of the triples. If they are equals |
| * <code>0</code> is returned. This will ensure that all triples with the |
| * same subjects are sorted correctly. However it does not sort predicates |
| * and objects! |
| */ |
| public static final Comparator<Triple> SUBJECT_COMPARATOR = new Comparator<Triple>() { |
| |
| @Override |
| public int compare(Triple a, Triple b) { |
| return compare(a.getSubject(), b.getSubject()); |
| } |
| |
| private int compare(BlankNodeOrIRI a, BlankNodeOrIRI b) { |
| int hashA = a.hashCode(); |
| int hashB = b.hashCode(); |
| if (hashA != hashB) { |
| return hashA > hashB ? 1 : -1; |
| } |
| return a.toString().compareTo(b.toString()); |
| } |
| |
| }; |
| } |