/* * 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.stanbol.entityhub.web.impl; import java.io.IOException; import java.io.OutputStream; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response.Status; import org.apache.commons.io.IOUtils; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.ConfigurationPolicy; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; import org.apache.stanbol.commons.namespaceprefix.NamespacePrefixService; import org.apache.stanbol.entityhub.core.utils.TimeUtils; import org.apache.stanbol.entityhub.servicesapi.defaults.DataTypeEnum; import org.apache.stanbol.entityhub.servicesapi.model.Entity; import org.apache.stanbol.entityhub.servicesapi.model.Reference; import org.apache.stanbol.entityhub.servicesapi.model.Representation; import org.apache.stanbol.entityhub.servicesapi.model.Text; import org.apache.stanbol.entityhub.servicesapi.query.QueryResultList; import org.apache.stanbol.entityhub.web.ModelWriter; import org.apache.stanbol.entityhub.web.fieldquery.FieldQueryToJsonUtils; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.component.ComponentContext; /** * Component that supports serialising Entityhub Model classes as * {@link MediaType#APPLICATION_JSON}. * * @author Rupert Westenthaler * */ @Component(immediate=true, metatype=true, policy=ConfigurationPolicy.OPTIONAL) @Service public class JsonModelWriter implements ModelWriter { /** * The list of supported media types as returned by {@link #supportedMediaTypes()} * containing only {@link MediaType#APPLICATION_JSON_TYPE} */ public static final List<MediaType> SUPPORTED_MEDIA_TYPES = Collections.singletonList(MediaType.APPLICATION_JSON_TYPE); /** * Allows to enable pretty format and setting the indent. If %lt;= 0 * pretty format is deactivated. */ @Property(intValue=JsonModelWriter.DEFAULT_INDENT) public static final String PROEPRTY_INDENT = "entityhub.web.writer.json.indent"; /** * The default for {@link #PROEPRTY_INDENT} is <code>-1</code>. */ public static final int DEFAULT_INDENT = -1; /** * The optional {@link NamespacePrefixService} is used for serialising * {@link QueryResultList}s that do use namespace prefixes */ @org.apache.felix.scr.annotations.Reference( cardinality=ReferenceCardinality.OPTIONAL_UNARY) protected NamespacePrefixService nsPrefixService; private int indent; @Activate protected void activate(ComponentContext ctx) throws ConfigurationException { Object value =ctx.getProperties().get(PROEPRTY_INDENT); if(value instanceof Number){ indent = ((Number)value).intValue(); } else if(value != null){ try { indent = Integer.parseInt(value.toString()); } catch(NumberFormatException e){ throw new ConfigurationException(PROEPRTY_INDENT, "The parsed indent MUST BE an Integer number (values <= 0 " + "will deactivate pretty format)"); } } else { indent = DEFAULT_INDENT; } } @Deactivate protected void deactivate(ComponentContext ctx){ indent = -1; } @Override public Class<? extends Representation> getNativeType() { return null; //no native type } @Override public List<MediaType> supportedMediaTypes() { return SUPPORTED_MEDIA_TYPES; } @Override public MediaType getBestMediaType(MediaType mediaType) { return MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType) ? MediaType.APPLICATION_JSON_TYPE : null; } @Override public void write(Representation rep, OutputStream out, MediaType mediaType) throws WebApplicationException, IOException { try { writeJsonObject(toJSON(rep), out,getCharset(mediaType)); } catch (JSONException e) { throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR); } } @Override public void write(Entity entity, OutputStream out, MediaType mediaType) throws WebApplicationException, IOException { try { writeJsonObject(toJSON(entity), out,getCharset(mediaType)); } catch (JSONException e) { throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR); } } @Override public void write(QueryResultList<?> result, OutputStream out, MediaType mediaType) throws WebApplicationException, IOException { try { writeJsonObject(toJSON(result,nsPrefixService), out,getCharset(mediaType)); } catch (JSONException e) { throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR); } } private JSONObject toJSON(Entity entity) throws JSONException { return convertEntityToJSON(entity); } /** * Writes the {@link JSONObject} to the stream * @param jObject the object to write * @param out the output stream * @param charset the charset * @throws IOException * @throws JSONException */ private void writeJsonObject(JSONObject jObject, OutputStream out, String charset) throws IOException, JSONException { IOUtils.write(indent > 0 ? jObject.toString(indent) : jObject.toString(), out, charset); } /** * @param mediaType * @return */ private String getCharset(MediaType mediaType) { String charset = mediaType.getParameters().get("charset"); if(charset == null){ charset = ModelWriter.DEFAULT_CHARSET; } return charset; } /** * @param entity * @return * @throws JSONException */ private JSONObject convertEntityToJSON(Entity entity) throws JSONException { JSONObject jSign; jSign = new JSONObject(); jSign.put("id", entity.getId()); jSign.put("site", entity.getSite()); // Representation rep = sign.getRepresentation(); jSign.put("representation", toJSON(entity.getRepresentation())); jSign.put("metadata", toJSON(entity.getMetadata())); return jSign; } /** * Converts the {@link Representation} to JSON * * @param jSign * @param rep * @throws JSONException */ private JSONObject toJSON(Representation rep) throws JSONException { JSONObject jRep = new JSONObject(); jRep.put("id", rep.getId()); for (Iterator<String> fields = rep.getFieldNames(); fields.hasNext();) { String field = fields.next(); Iterator<Object> values = rep.get(field); if (values.hasNext()) { jRep.put(field, convertFieldValuesToJSON(values)); } } return jRep; } /** * @param values Iterator over all the values to add * @return The {@link JSONArray} with all the values as {@link JSONObject} * @throws JSONException */ private JSONArray convertFieldValuesToJSON(Iterator<?> values) throws JSONException { JSONArray jValues = new JSONArray(); while (values.hasNext()) { jValues.put(convertFieldValueToJSON(values.next())); } return jValues; } /** * The value to write. Special support for {@link Reference} and {@link Text}. * The {@link #toString()} Method is used to write the "value" key. * * @param value the value * @return the {@link JSONObject} representing the value * @throws JSONException */ private JSONObject convertFieldValueToJSON(Object value) throws JSONException { JSONObject jValue = new JSONObject(); if (value instanceof Reference) { jValue.put("type", "reference"); jValue.put("xsd:datatype", DataTypeEnum.AnyUri.getShortName()); jValue.put("value", ((Reference)value).getReference()); } else if (value instanceof Text) { jValue.put("type", "text"); jValue.put("xml:lang", ((Text) value).getLanguage()); jValue.put("value", ((Text)value).getText()); } else if(value instanceof Date){ jValue.put("type", "value"); jValue.put("value", TimeUtils.toString(DataTypeEnum.DateTime, (Date)value)); jValue.put("xsd:datatype", DataTypeEnum.DateTime.getShortName()); } else { jValue.put("type", "value"); Set<DataTypeEnum> dataTypes = DataTypeEnum.getPrimaryDataTypes(value.getClass()); if(!dataTypes.isEmpty()){ jValue.put("xsd:datatype", dataTypes.iterator().next().getShortName()); } else { jValue.put("xsd:datatype", DataTypeEnum.String.getShortName()); } jValue.put("value", value); } return jValue; } private <T> JSONObject toJSON(QueryResultList<?> resultList, NamespacePrefixService nsPrefixService) throws JSONException{ JSONObject jResultList = new JSONObject(); if(resultList.getQuery() != null){ jResultList.put("query", FieldQueryToJsonUtils.toJSON(resultList.getQuery(),nsPrefixService)); } jResultList.put("results", convertResultsToJSON(resultList,resultList.getType())); return jResultList; } private <T> JSONArray convertResultsToJSON(Iterable<?> results,Class<?> type) throws JSONException{ JSONArray jResults = new JSONArray(); if(String.class.isAssignableFrom(type)){ for(Object result : results){ jResults.put(result); } } else if(Representation.class.isAssignableFrom(type)){ for(Object result : results){ jResults.put(toJSON((Representation)result)); } } else if(Entity.class.isAssignableFrom(type)){ for(Object result : results){ jResults.put(toJSON((Entity)result)); } } return jResults; } }