/** * * Copyright * 2009-2015 Jayway Products AB * 2016-2017 Föreningen Sambruk * * Licensed under AGPL, Version 3.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.gnu.org/licenses/agpl.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 se.streamsource.streamflow.web.domain.util; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.qi4j.api.entity.EntityComposite; import org.qi4j.api.entity.EntityReference; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.unitofwork.UnitOfWork; import org.qi4j.api.usecase.Usecase; import org.qi4j.api.usecase.UsecaseBuilder; import org.qi4j.api.util.Function; import org.qi4j.api.util.Iterables; import org.qi4j.spi.Qi4jSPI; import org.qi4j.spi.entity.EntityDescriptor; import org.qi4j.spi.entity.EntityState; import org.qi4j.spi.entity.EntityType; import org.qi4j.spi.entity.ManyAssociationState; import org.qi4j.spi.entity.association.AssociationDescriptor; import org.qi4j.spi.entity.association.ManyAssociationDescriptor; import org.qi4j.spi.entitystore.EntityNotFoundException; import org.qi4j.spi.entitystore.EntityStore; import org.qi4j.spi.entitystore.EntityStoreUnitOfWork; import org.qi4j.spi.property.PropertyType; import org.qi4j.spi.structure.ModuleSPI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.streamsource.streamflow.util.Primitives; import java.lang.reflect.*; import java.util.ArrayList; import java.util.Collections; import java.util.Map; /** * Converts an entity to a json string aggregating all associations and many associations too. */ public class ToJson { private static Logger logger = LoggerFactory.getLogger( ToJson.class.getName() ); ModuleSPI module; EntityStore entityStore; EntityStoreUnitOfWork uow; public ToJson(@Structure ModuleSPI module, @Service EntityStore entityStore ) { this.module = module; this.entityStore = entityStore; this.uow = entityStore.newUnitOfWork( UsecaseBuilder.newUsecase("toJson"), module ); } /** * <pre> * { * "_identity": "ENTITY-IDENTITY", * "_types": [ "All", "Entity", "types" ], * "property.name": property.value, * "association.name": "ASSOCIATED-IDENTITY", * "manyassociation.name": [ "ASSOCIATED", "IDENTITIES" ] * } * </pre> */ public String toJSON( EntityState state, boolean aggregateAssociations ) { long start = System.nanoTime(); JSONObject json = null; try { json = new JSONObject(); json.put("_identity", state.identity()); EntityDescriptor entityDesc = state.entityDescriptor(); EntityType entityType = entityDesc.entityType(); json.put("_type", entityType.toString()); // Properties for( PropertyType propType : entityType.properties() ) { if( propType.queryable() ) { String key = propType.qualifiedName().name(); Object value = state.getProperty(propType.qualifiedName()); if( value == null || Primitives.isPrimitiveValue(value) ) { json.put( key, value ); } else { // TODO Theses tests are pretty fragile, find a better way to fix this, Jackson API should behave better String serialized = propType.type().toJSON(value).toString(); if( serialized.startsWith( "{" ) ) { json.put( key, new JSONObject( serialized ) ); } else if( serialized.startsWith( "[" ) ) { json.put( key, new JSONArray( serialized ) ); } else { json.put( key, serialized ); } } } } // Associations for( AssociationDescriptor assocDesc : entityDesc.state().associations() ) { if( assocDesc.associationType().queryable() ) { String key = assocDesc.qualifiedName().name(); EntityReference associated = state.getAssociation(assocDesc.qualifiedName()); Object value; if( associated == null ) { value = null; } else { if( aggregateAssociations ) { try { EntityState assocState = uow.getEntityState( EntityReference.parseEntityReference( associated.identity() ) ); value = new JSONObject( toJSON( assocState, false ) ); } catch ( EntityNotFoundException e ) { value = new JSONObject( Collections.singletonMap("identity", associated.identity() + " aggregation impossible") ); } } else { value = new JSONObject( Collections.singletonMap("identity", associated.identity()) ); } } json.put( key, value ); } } // ManyAssociations for( ManyAssociationDescriptor manyAssocDesc : entityDesc.state().manyAssociations() ) { if( manyAssocDesc.manyAssociationType().queryable() ) { String key = manyAssocDesc.qualifiedName().name(); JSONArray array = new JSONArray(); ManyAssociationState associateds = state.getManyAssociation(manyAssocDesc.qualifiedName()); for( EntityReference associated : associateds ) { if( aggregateAssociations ) { try { EntityState assocState = uow.getEntityState(EntityReference.parseEntityReference(associated.identity())); array.put( new JSONObject( toJSON( assocState, false ) ) ); } catch ( EntityNotFoundException e ) { array.put( new JSONObject( Collections.singletonMap("identity", associated.identity() + " aggregation impossible") ) ); } } else { array.put( new JSONObject( Collections.singletonMap( "identity", associated.identity() ) ) ); } } json.put( key, array ); } } return json.toString(); } catch( JSONException e ) { logger.info("Faild to convert Entity to Json: " + state.identity(), e); throw new RuntimeException("Faild to convert Entity to Json: " + state.identity(), e); } } private Function<Type, String> toClassName() { return new Function<Type, String>() { public String map( Type type ) { return RAW_CLASS.map( type ).getName(); } }; } /** * Function that extract the raw class of a type. */ private final Function<Type, Class<?>> RAW_CLASS = new Function<Type, Class<?>>() { public Class<?> map( Type genericType ) { // Calculate raw type if( genericType instanceof Class ) { return (Class<?>) genericType; } else if( genericType instanceof ParameterizedType) { return (Class<?>) ( (ParameterizedType) genericType ).getRawType(); } else if( genericType instanceof TypeVariable) { return (Class<?>) ( (TypeVariable) genericType ).getGenericDeclaration(); } else if( genericType instanceof WildcardType) { return (Class<?>) ( (WildcardType) genericType ).getUpperBounds()[ 0 ]; } else if( genericType instanceof GenericArrayType) { Object temp = Array.newInstance( (Class<?>) ( (GenericArrayType) genericType ).getGenericComponentType(), 0 ); return temp.getClass(); } throw new IllegalArgumentException( "Could not extract the raw class of " + genericType ); } }; }