/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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.opencastproject.metadata.dublincore;
import static com.entwinemedia.fn.Equality.ne;
import static org.opencastproject.metadata.dublincore.DublinCore.LANGUAGE_UNDEFINED;
import org.opencastproject.mediapackage.EName;
import com.entwinemedia.fn.data.Opt;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Parse a DublinCore catalog from JSON.
* <p>
* <strong>Known limitations:</strong> Encoding schemas can currently only be from the
* {@link DublinCore#TERMS_NS_URI} namespace using the {@link DublinCore#TERMS_NS_PREFIX}
* since the JSON format does not serialize namespace bindings. Example: <code>dcterms:W3CDTF</code>
*/
@ParametersAreNonnullByDefault
public final class DublinCoreJsonFormat {
private DublinCoreJsonFormat() {
}
/**
* Read a JSON encoded catalog from a stream.
*/
@Nonnull
public static DublinCoreCatalog read(InputStream json) throws IOException, ParseException {
return read((JSONObject) new JSONParser().parse(new InputStreamReader(json)));
}
/**
* Read a JSON encoded catalog from a string.
*/
@Nonnull
public static DublinCoreCatalog read(String json) throws IOException, ParseException {
return read((JSONObject) new JSONParser().parse(json));
}
/**
* Reads values from a JSON object into a new DublinCore catalog.
*/
@SuppressWarnings("unchecked")
@Nonnull
public static DublinCoreCatalog read(JSONObject json) {
// Use a standard catalog to get the required namespace bindings in order to be able
// to parse standard DublinCore encoding schemes.
// See http://dublincore.org/documents/dc-xml-guidelines/, section 5.2, recommendation 7 for details.
// TODO the JSON representation should serialize the contained bindings like XML to be able to
// reconstruct a catalog from the serialization alone without the need to rely on bindings, registered
// before.
final DublinCoreCatalog dc = DublinCores.mkStandard();
final Set<Entry<String, JSONObject>> namespaceEntrySet = json.entrySet();
for (Entry<String, JSONObject> namespaceEntry : namespaceEntrySet) { // e.g. http://purl.org/dc/terms/
final String namespace = namespaceEntry.getKey();
final JSONObject namespaceObj = namespaceEntry.getValue();
final Set<Entry<String, JSONArray>> entrySet = namespaceObj.entrySet();
for (final Entry<String, JSONArray> entry : entrySet) { // e.g. title
final String key = entry.getKey();
final JSONArray values = entry.getValue();
for (final Object valueObject : values) {
final JSONObject value = (JSONObject) valueObject;
// the value
final String valueString = (String) value.get("value");
// the language
final String lang;
{
final String l = (String) value.get("lang");
lang = l != null ? l : LANGUAGE_UNDEFINED;
}
// the encoding scheme
final EName encodingScheme;
{
final String s = (String) value.get("type");
encodingScheme = s != null ? dc.toEName(s) : null;
}
// add the new value to this DC document
dc.add(new EName(namespace, key), valueString, lang, encodingScheme);
}
}
}
return dc;
}
/**
* Converts the catalog to JSON object.
*
* @return JSON object
*/
@SuppressWarnings("unchecked")
@Nonnull
public static JSONObject writeJsonObject(DublinCoreCatalog dc) {
// The top-level json object
final JSONObject json = new JSONObject();
// First collect all namespaces
final SortedSet<String> namespaces = new TreeSet<String>();
final Set<Entry<EName, List<DublinCoreValue>>> values = dc.getValues().entrySet();
for (final Entry<EName, List<DublinCoreValue>> entry : values) {
namespaces.add(entry.getKey().getNamespaceURI());
}
// Add a json object for each namespace
for (String namespace : namespaces) {
json.put(namespace, new JSONObject());
}
// Add the data into the appropriate array
for (final Entry<EName, List<DublinCoreValue>> entry : values) {
final EName ename = entry.getKey();
final String namespace = ename.getNamespaceURI();
final String localName = ename.getLocalName();
final JSONObject namespaceObject = (JSONObject) json.get(namespace);
final JSONArray localNameArray;
{
final JSONArray ns = (JSONArray) namespaceObject.get(localName);
if (ns != null) {
localNameArray = ns;
} else {
localNameArray = new JSONArray();
namespaceObject.put(localName, localNameArray);
}
}
for (DublinCoreValue value : entry.getValue()) {
final String lang = value.getLanguage();
final Opt<EName> encScheme = value.getEncodingScheme();
final JSONObject v = new JSONObject();
v.put("value", value.getValue());
if (ne(DublinCore.LANGUAGE_UNDEFINED, lang)) {
v.put("lang", lang);
}
for (EName e : encScheme) {
v.put("type", dc.toQName(e));
}
localNameArray.add(v);
}
}
return json;
}
}