blob: 9a0266173c68ab4f29eca69564ec55c3976225ca [file] [log] [blame]
/*
* 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.openjpa.persistence.jest;
import static org.apache.openjpa.persistence.jest.Constants.MIME_TYPE_JSON;
import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Metamodel;
import org.apache.openjpa.json.JSON;
import org.apache.openjpa.json.JSONObject;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.StoreContext;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.persistence.meta.Members;
/**
* Marshals a root instance and its persistent closure as JSON object.
* The closure is resolved against the persistence context that contains the root instance.
* The JSON format introduces a $id and $ref to address reference that pure JSON does not.
*
* @author Pinaki Poddar
*
*/
public class JSONObjectFormatter implements ObjectFormatter<JSON> {
@Override
public String getMimeType() {
return MIME_TYPE_JSON;
}
public void encode(Object obj, JPAServletContext ctx) {
if (obj instanceof OpenJPAStateManager) {
try {
JSON result = encodeManagedInstance((OpenJPAStateManager)obj,
ctx.getPersistenceContext().getMetamodel());
PrintWriter writer = ctx.getResponse().getWriter();
writer.println(result.toString());
} catch (Exception e) {
throw new ProcessingException(ctx, e);
}
} else {
throw new RuntimeException(this + " does not know how to encode " + obj);
}
return;
}
@Override
public JSON writeOut(Collection<OpenJPAStateManager> sms, Metamodel model, String title, String desc,
String uri, OutputStream out) throws IOException {
JSON json = encode(sms,model);
out.write(json.toString().getBytes());
return json;
}
@Override
public JSON encode(Collection<OpenJPAStateManager> sms, Metamodel model) {
return encodeManagedInstances(sms, model);
}
/**
* Encodes the given managed instance into a new XML element as a child of the given parent node.
*
* @param sm a managed instance, can be null.
* @param parent the parent node to which the new node be attached.
*/
private JSON encodeManagedInstance(final OpenJPAStateManager sm, Metamodel model) {
return encodeManagedInstance(sm, new HashSet<>(), 0, false, model);
}
private JSON encodeManagedInstances(final Collection<OpenJPAStateManager> sms, Metamodel model) {
JSONObject.Array result = new JSONObject.Array();
for (OpenJPAStateManager sm : sms) {
result.add(encodeManagedInstance(sm, new HashSet<>(), 0, false, model));
}
return result;
}
/**
* Encodes the closure of a persistent instance into a XML element.
*
* @param sm the managed instance to be encoded. Can be null.
* @param parent the parent XML element to which the new XML element be added. Must not be null. Must be
* owned by a document.
* @param visited the persistent instances that had been encoded already. Must not be null or immutable.
*
* @return the new element. The element has been appended as a child to the given parent in this method.
*/
private JSONObject encodeManagedInstance(final OpenJPAStateManager sm, final Set<OpenJPAStateManager> visited,
int indent, boolean indentPara, Metamodel model) {
if (visited == null) {
throw new IllegalArgumentException("null closure for encoder");
}
if (sm == null) {
return null;
}
boolean ref = !visited.add(sm);
JSONObject root = new JSONObject(typeOf(sm), sm.getObjectId(), ref);
if (ref) {
return root;
}
BitSet loaded = sm.getLoaded();
StoreContext ctx = (StoreContext)sm.getGenericContext();
List<Attribute<?, ?>> attrs = MetamodelHelper.getAttributesInOrder(sm.getMetaData(), model);
for (Attribute<?, ?> attr : attrs) {
FieldMetaData fmd = ((Members.Member<?, ?>) attr).fmd;
if (!loaded.get(fmd.getIndex()))
continue;
Object value = sm.fetch(fmd.getIndex());
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.BOOLEAN:
case JavaTypes.BYTE:
case JavaTypes.CHAR:
case JavaTypes.DOUBLE:
case JavaTypes.FLOAT:
case JavaTypes.INT:
case JavaTypes.LONG:
case JavaTypes.SHORT:
case JavaTypes.BOOLEAN_OBJ:
case JavaTypes.BYTE_OBJ:
case JavaTypes.CHAR_OBJ:
case JavaTypes.DOUBLE_OBJ:
case JavaTypes.FLOAT_OBJ:
case JavaTypes.INT_OBJ:
case JavaTypes.LONG_OBJ:
case JavaTypes.SHORT_OBJ:
case JavaTypes.BIGDECIMAL:
case JavaTypes.BIGINTEGER:
case JavaTypes.DATE:
case JavaTypes.NUMBER:
case JavaTypes.CALENDAR:
case JavaTypes.LOCALE:
case JavaTypes.STRING:
case JavaTypes.ENUM:
root.set(fmd.getName(), value);
break;
case JavaTypes.PC:
if (value == null) {
root.set(fmd.getName(), null);
}
else {
root.set(fmd.getName(), encodeManagedInstance(ctx.getStateManager(value), visited,
indent + 1, false, model));
}
break;
case JavaTypes.ARRAY:
Object[] values = (Object[]) value;
value = Arrays.asList(values);
// no break;
case JavaTypes.COLLECTION:
if (value == null) {
root.set(fmd.getName(), null);
break;
}
Collection<?> members = (Collection<?>) value;
JSONObject.Array array = new JSONObject.Array();
root.set(fmd.getName(), array);
if (members.isEmpty()) {
break;
}
boolean basic = fmd.getElement().getTypeMetaData() == null;
for (Object o : members) {
if (o == null) {
array.add(null);
}
else {
if (basic) {
array.add(o);
}
else {
array.add(encodeManagedInstance(ctx.getStateManager(o), visited, indent + 1, true,
model));
}
}
}
break;
case JavaTypes.MAP:
if (value == null) {
root.set(fmd.getName(), null);
break;
}
Set<Map.Entry> entries = ((Map) value).entrySet();
JSONObject.KVMap map = new JSONObject.KVMap();
root.set(fmd.getName(), map);
if (entries.isEmpty()) {
break;
}
boolean basicKey = fmd.getElement().getTypeMetaData() == null;
boolean basicValue = fmd.getValue().getTypeMetaData() == null;
for (Map.Entry<?, ?> e : entries) {
Object k = e.getKey();
Object v = e.getValue();
if (!basicKey) {
k = encodeManagedInstance(ctx.getStateManager(k), visited, indent + 1, true, model);
}
if (!basicValue) {
v = encodeManagedInstance(ctx.getStateManager(e.getValue()), visited,
indent + 1, false, model);
}
map.put(k, v);
}
break;
case JavaTypes.INPUT_STREAM:
case JavaTypes.INPUT_READER:
root.set(fmd.getName(), streamToString(value));
break;
case JavaTypes.PC_UNTYPED:
case JavaTypes.OBJECT:
case JavaTypes.OID:
root.set(fmd.getName(), "***UNSUPPORTED***");
}
}
return root;
}
String typeOf(OpenJPAStateManager sm) {
return sm.getMetaData().getDescribedType().getSimpleName();
}
/**
* Convert the given stream (either an InutStream or a Reader) to a String
* to be included in CDATA section of a XML document.
*
* @param value the field value to be converted. Can not be null
*/
String streamToString(Object value) {
Reader reader = null;
if (value instanceof InputStream) {
reader = new BufferedReader(new InputStreamReader((InputStream)value));
} else if (value instanceof Reader) {
reader = (Reader)value;
} else {
throw new RuntimeException();
}
CharArrayWriter writer = new CharArrayWriter();
try {
for (int c; (c = reader.read()) != -1;) {
writer.write(c);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return writer.toString();
}
@Override
public JSON encode(Metamodel model) {
// TODO Auto-generated method stub
return null;
}
@Override
public JSON writeOut(Metamodel model, String title, String desc, String uri, OutputStream out) throws IOException {
throw new UnsupportedOperationException();
}
}