http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializationService.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializationService.java b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializationService.java new file mode 100644 index 0000000..2b662ea --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializationService.java @@ -0,0 +1,533 @@ +/* + * 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.polygene.serialization.javaxjson; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.function.BiFunction; +import java.util.function.Function; +import javax.json.Json; +import javax.json.JsonNumber; +import javax.json.JsonString; +import javax.json.JsonValue; +import org.apache.polygene.api.entity.EntityReference; +import org.apache.polygene.api.identity.Identity; +import org.apache.polygene.api.identity.StringIdentity; +import org.apache.polygene.api.injection.scope.This; +import org.apache.polygene.api.injection.scope.Uses; +import org.apache.polygene.api.mixin.Mixins; +import org.apache.polygene.api.serialization.SerializationException; +import org.apache.polygene.api.service.ServiceActivation; +import org.apache.polygene.api.service.ServiceDescriptor; +import org.apache.polygene.api.type.ValueType; + +// TODO Move into JavaxJsonSerialization +// TODO Do the same on XML & MessagePack +@Mixins( JavaxJsonSerializationService.Activation.class ) +public interface JavaxJsonSerializationService extends JavaxJsonSerialization, ServiceActivation +{ + class Activation implements ServiceActivation + { + @Uses + private ServiceDescriptor descriptor; + + @This + private JavaxJsonAdapters adapters; + + private boolean registrationDone = false; + + @Override + public void activateService() + { + if( !registrationDone ) + { + registerCustomAdapters(); + registerBaseAdapters(); + registrationDone = true; + } + } + + @Override + public void passivateService() {} + + private void registerCustomAdapters() + { + JavaxJsonSettings.orDefault( descriptor.metaInfo( JavaxJsonSettings.class ) ) + .getAdapters() + .forEach( ( valueType, adapter ) -> adapters.registerAdapter( valueType, adapter ) ); + } + + private void registerBaseAdapters() + { + // Primitive Value types + adapters.registerAdapter( ValueType.STRING, new StringAdapter() ); + adapters.registerAdapter( ValueType.CHARACTER, new CharacterAdapter() ); + adapters.registerAdapter( ValueType.BOOLEAN, new BooleanAdapter() ); + adapters.registerAdapter( ValueType.INTEGER, new IntegerAdapter() ); + adapters.registerAdapter( ValueType.LONG, new LongAdapter() ); + adapters.registerAdapter( ValueType.SHORT, new ShortAdapter() ); + adapters.registerAdapter( ValueType.BYTE, new ByteAdapter() ); + adapters.registerAdapter( ValueType.FLOAT, new FloatAdapter() ); + adapters.registerAdapter( ValueType.DOUBLE, new DoubleAdapter() ); + + // Number types + adapters.registerAdapter( ValueType.BIG_DECIMAL, new BigDecimalAdapter() ); + adapters.registerAdapter( ValueType.BIG_INTEGER, new BigIntegerAdapter() ); + + // Date types + adapters.registerAdapter( ValueType.INSTANT, new InstantAdapter() ); + adapters.registerAdapter( ValueType.ZONED_DATE_TIME, new ZonedDateTimeAdapter() ); + adapters.registerAdapter( ValueType.OFFSET_DATE_TIME, new OffsetDateTimeAdapter() ); + adapters.registerAdapter( ValueType.LOCAL_DATE_TIME, new LocalDateTimeAdapter() ); + adapters.registerAdapter( ValueType.LOCAL_DATE, new LocalDateAdapter() ); + adapters.registerAdapter( ValueType.LOCAL_TIME, new LocalTimeAdapter() ); + adapters.registerAdapter( ValueType.DURATION, new DurationAdapter() ); + adapters.registerAdapter( ValueType.PERIOD, new PeriodAdapter() ); + + // Other supported types + adapters.registerAdapter( ValueType.IDENTITY, new IdentityAdapter() ); + adapters.registerAdapter( ValueType.ENTITY_REFERENCE, new EntityReferenceAdapter() ); + } + + private static abstract class ToJsonStringAdapter<T> implements JavaxJsonAdapter<T> + { + @Override + public JsonValue serialize( Object object, Function<Object, JsonValue> serializeFunction ) + { + return JavaxJson.toJsonString( object ); + } + } + + private static class StringAdapter extends ToJsonStringAdapter<String> + { + @Override + public Class<String> type() { return String.class; } + + @Override + public String deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return JavaxJson.asString( json ); + } + } + + private static class CharacterAdapter extends ToJsonStringAdapter<Character> + { + @Override + public Class<Character> type() { return Character.class; } + + @Override + public Character deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + String string = JavaxJson.asString( json ); + return string.isEmpty() ? null : string.charAt( 0 ); + } + } + + private static class BooleanAdapter implements JavaxJsonAdapter<Boolean> + { + @Override + public Class<Boolean> type() { return Boolean.class; } + + @Override + public JsonValue serialize( Object object, Function<Object, JsonValue> serializeFunction ) + { + return type().cast( object ) ? JsonValue.TRUE : JsonValue.FALSE; + } + + @Override + public Boolean deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + switch( json.getValueType() ) + { + case TRUE: + return true; + case FALSE: + return false; + case NULL: + return null; + case NUMBER: + return ( (JsonNumber) json ).doubleValue() > 0; + case STRING: + return Boolean.valueOf( ( (JsonString) json ).getString() ); + default: + throw new SerializationException( "Don't know how to deserialize Boolean from " + json ); + } + } + } + + private static class IntegerAdapter implements JavaxJsonAdapter<Integer> + { + @Override + public Class<Integer> type() { return Integer.class; } + + @Override + public JsonValue serialize( Object object, Function<Object, JsonValue> serializeFunction ) + { + return Json.createObjectBuilder().add( "value", type().cast( object ) ).build() + .getJsonNumber( "value" ); + } + + @Override + public Integer deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + switch( json.getValueType() ) + { + case NULL: + return null; + case NUMBER: + return ( (JsonNumber) json ).intValueExact(); + case STRING: + String string = ( (JsonString) json ).getString(); + return string.isEmpty() ? 0 : Integer.parseInt( string ); + default: + throw new SerializationException( "Don't know how to deserialize Integer from " + json ); + } + } + } + + private static class LongAdapter implements JavaxJsonAdapter<Long> + { + @Override + public Class<Long> type() { return Long.class; } + + @Override + public JsonValue serialize( Object object, Function<Object, JsonValue> serializeFunction ) + { + return Json.createObjectBuilder().add( "value", type().cast( object ) ).build().getJsonNumber( + "value" ); + } + + @Override + public Long deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + switch( json.getValueType() ) + { + case NULL: + return null; + case NUMBER: + return ( (JsonNumber) json ).longValueExact(); + case STRING: + String string = ( (JsonString) json ).getString(); + return string.isEmpty() ? 0L : Long.parseLong( string ); + default: + throw new SerializationException( "Don't know how to deserialize Long from " + json ); + } + } + } + + private static class ShortAdapter implements JavaxJsonAdapter<Short> + { + @Override + public Class<Short> type() { return Short.class; } + + @Override + public JsonValue serialize( Object object, Function<Object, JsonValue> serializeFunction ) + { + return Json.createObjectBuilder().add( "value", type().cast( object ) ).build() + .getJsonNumber( "value" ); + } + + @Override + public Short deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + switch( json.getValueType() ) + { + case NULL: + return null; + case NUMBER: + return (short) ( (JsonNumber) json ).intValueExact(); + case STRING: + String string = ( (JsonString) json ).getString(); + return string.isEmpty() ? 0 : Short.parseShort( string ); + default: + throw new SerializationException( "Don't know how to deserialize Short from " + json ); + } + } + } + + private static class ByteAdapter implements JavaxJsonAdapter<Byte> + { + @Override + public Class<Byte> type() { return Byte.class; } + + @Override + public JsonValue serialize( Object object, Function<Object, JsonValue> serializeFunction ) + { + return Json.createObjectBuilder().add( "value", type().cast( object ) ).build() + .getJsonNumber( "value" ); + } + + @Override + public Byte deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + switch( json.getValueType() ) + { + case NULL: + return null; + case NUMBER: + return (byte) ( (JsonNumber) json ).intValueExact(); + case STRING: + String string = ( (JsonString) json ).getString(); + return string.isEmpty() ? 0 : Byte.parseByte( string ); + default: + throw new SerializationException( "Don't know how to deserialize Byte from " + json ); + } + } + } + + private static class FloatAdapter implements JavaxJsonAdapter<Float> + { + @Override + public Class<Float> type() { return Float.class; } + + @Override + public JsonValue serialize( Object object, Function<Object, JsonValue> serializeFunction ) + { + return Json.createObjectBuilder().add( "value", type().cast( object ) ).build() + .getJsonNumber( "value" ); + } + + @Override + public Float deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + switch( json.getValueType() ) + { + case NULL: + return null; + case NUMBER: + return (float) ( (JsonNumber) json ).doubleValue(); + case STRING: + String string = ( (JsonString) json ).getString(); + return string.isEmpty() ? 0F : Float.parseFloat( string ); + default: + throw new SerializationException( "Don't know how to deserialize Float from " + json ); + } + } + } + + private static class DoubleAdapter implements JavaxJsonAdapter<Double> + { + @Override + public Class<Double> type() { return Double.class; } + + @Override + public JsonValue serialize( Object object, Function<Object, JsonValue> serializeFunction ) + { + return Json.createObjectBuilder().add( "value", type().cast( object ) ).build() + .getJsonNumber( "value" ); + } + + @Override + public Double deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + switch( json.getValueType() ) + { + case NULL: + return null; + case NUMBER: + return ( (JsonNumber) json ).doubleValue(); + case STRING: + String string = ( (JsonString) json ).getString(); + return string.isEmpty() ? 0D : Double.parseDouble( string ); + default: + throw new SerializationException( "Don't know how to deserialize Double from " + json ); + } + } + } + + private static class BigDecimalAdapter extends ToJsonStringAdapter<BigDecimal> + { + @Override + public Class<BigDecimal> type() { return BigDecimal.class; } + + @Override + public BigDecimal deserialize( JsonValue json, + BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + switch( json.getValueType() ) + { + case NULL: + return null; + case NUMBER: + return new BigDecimal( json.toString() ); + case STRING: + return new BigDecimal( ( (JsonString) json ).getString() ); + default: + throw new SerializationException( + "Don't know how to deserialize BigDecimal from " + json ); + } + } + } + + private static class BigIntegerAdapter extends ToJsonStringAdapter<BigInteger> + { + @Override + public Class<BigInteger> type() { return BigInteger.class; } + + @Override + public BigInteger deserialize( JsonValue json, + BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + switch( json.getValueType() ) + { + case NULL: + return null; + case NUMBER: + return new BigInteger( json.toString() ); + case STRING: + return new BigInteger( ( (JsonString) json ).getString() ); + default: + throw new SerializationException( + "Don't know how to deserialize BigInteger from " + json ); + } + } + } + + private static class PeriodAdapter extends ToJsonStringAdapter<Period> + { + @Override + public Class<Period> type() { return Period.class; } + + @Override + public Period deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return Period.parse( JavaxJson.asString( json ) ); + } + } + + private static class DurationAdapter extends ToJsonStringAdapter<Duration> + { + @Override + public Class<Duration> type() { return Duration.class; } + + @Override + public Duration deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return Duration.parse( JavaxJson.asString( json ) ); + } + } + + private static class LocalTimeAdapter extends ToJsonStringAdapter<LocalTime> + { + @Override + public Class<LocalTime> type() { return LocalTime.class; } + + @Override + public LocalTime deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return LocalTime.parse( JavaxJson.asString( json ) ); + } + } + + private static class LocalDateAdapter extends ToJsonStringAdapter<LocalDate> + { + @Override + public Class<LocalDate> type() { return LocalDate.class; } + + @Override + public LocalDate deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return LocalDate.parse( JavaxJson.asString( json ) ); + } + } + + private static class LocalDateTimeAdapter extends ToJsonStringAdapter<LocalDateTime> + { + @Override + public Class<LocalDateTime> type() { return LocalDateTime.class; } + + @Override + public LocalDateTime deserialize( JsonValue json, + BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return LocalDateTime.parse( JavaxJson.asString( json ) ); + } + } + + private static class OffsetDateTimeAdapter extends ToJsonStringAdapter<OffsetDateTime> + { + @Override + public Class<OffsetDateTime> type() { return OffsetDateTime.class; } + + @Override + public OffsetDateTime deserialize( JsonValue json, + BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return OffsetDateTime.parse( JavaxJson.asString( json ) ); + } + } + + private static class ZonedDateTimeAdapter extends ToJsonStringAdapter<ZonedDateTime> + { + @Override + public Class<ZonedDateTime> type() { return ZonedDateTime.class; } + + @Override + public ZonedDateTime deserialize( JsonValue json, + BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return ZonedDateTime.parse( JavaxJson.asString( json ) ); + } + } + + private static class InstantAdapter extends ToJsonStringAdapter<Instant> + { + @Override + public Class<Instant> type() { return Instant.class; } + + @Override + public Instant deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return Instant.parse( JavaxJson.asString( json ) ); + } + } + + private static class IdentityAdapter extends ToJsonStringAdapter<Identity> + { + @Override + public Class<Identity> type() { return Identity.class; } + + @Override + public Identity deserialize( JsonValue json, BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return StringIdentity.fromString( JavaxJson.asString( json ) ); + } + } + + private static class EntityReferenceAdapter extends ToJsonStringAdapter<EntityReference> + { + @Override + public Class<EntityReference> type() { return EntityReference.class; } + + @Override + public EntityReference deserialize( JsonValue json, + BiFunction<JsonValue, ValueType, Object> deserializeFunction ) + { + return EntityReference.parseEntityReference( JavaxJson.asString( json ) ); + } + } + } +}
http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializer.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializer.java b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializer.java new file mode 100644 index 0000000..17f7ad2 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializer.java @@ -0,0 +1,213 @@ +/* + * 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.polygene.serialization.javaxjson; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.UncheckedIOException; +import java.util.Base64; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonString; +import javax.json.JsonValue; +import org.apache.polygene.api.PolygeneAPI; +import org.apache.polygene.api.association.AssociationStateHolder; +import org.apache.polygene.api.composite.CompositeInstance; +import org.apache.polygene.api.injection.scope.This; +import org.apache.polygene.api.injection.scope.Uses; +import org.apache.polygene.api.service.ServiceDescriptor; +import org.apache.polygene.api.type.EnumType; +import org.apache.polygene.api.type.MapType; +import org.apache.polygene.api.type.ValueCompositeType; +import org.apache.polygene.api.type.ValueType; +import org.apache.polygene.api.value.ValueComposite; +import org.apache.polygene.api.value.ValueDescriptor; +import org.apache.polygene.spi.serialization.AbstractTextSerializer; +import org.apache.polygene.spi.serialization.JsonSerializer; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.toList; +import static org.apache.polygene.api.util.Collectors.toMap; + +public class JavaxJsonSerializer extends AbstractTextSerializer implements JsonSerializer +{ + @This + private JavaxJsonAdapters adapters; + + @Uses + private ServiceDescriptor descriptor; + + @Override + public <T> Function<T, JsonValue> toJsonFunction( Options options ) + { + return object -> doSerialize( options, object, true ); + } + + private JsonValue doSerialize( Options options, Object object, boolean root ) + { + if( object == null ) + { + return JsonValue.NULL; + } + Class<?> objectClass = object.getClass(); + JavaxJsonAdapter<?> adapter = adapters.adapterFor( objectClass ); + if( adapter != null ) + { + return adapter.serialize( object, obj -> doSerialize( options, obj, false ) ); + } + if( EnumType.isEnum( objectClass ) ) + { + return JavaxJson.toJsonString( object.toString() ); + } + if( ValueCompositeType.isValueComposite( objectClass ) ) + { + return serializeValueComposite( options, object, root ); + } + if( MapType.isMap( objectClass ) ) + { + return serializeMap( options, (Map<?, ?>) object ); + } + if( Iterable.class.isAssignableFrom( objectClass ) ) + { + return serializeIterable( options, (Iterable<?>) object ); + } + if( Stream.class.isAssignableFrom( objectClass ) ) + { + return serializeStream( options, (Stream<?>) object ); + } + // Fallback to Java Serialization in Base 64 + // Include all arrays! + return serializeBase64( object ); + } + + private JsonObject serializeValueComposite( Options options, Object composite, boolean root ) + { + CompositeInstance instance = PolygeneAPI.FUNCTION_COMPOSITE_INSTANCE_OF.apply( (ValueComposite) composite ); + ValueDescriptor descriptor = (ValueDescriptor) instance.descriptor(); + AssociationStateHolder state = (AssociationStateHolder) instance.state(); + ValueCompositeType valueType = descriptor.valueType(); + + JsonObjectBuilder builder = Json.createObjectBuilder(); + valueType.properties().forEach( + property -> builder.add( + property.qualifiedName().name(), + doSerialize( options, state.propertyFor( property.accessor() ).get(), false ) ) ); + valueType.associations().forEach( + association -> builder.add( + association.qualifiedName().name(), + doSerialize( options, state.associationFor( association.accessor() ).reference(), false ) ) ); + valueType.manyAssociations().forEach( + association -> builder.add( + association.qualifiedName().name(), + doSerialize( options, state.manyAssociationFor( association.accessor() ).references() + .collect( toList() ), + false ) ) ); + valueType.namedAssociations().forEach( + association -> builder.add( + association.qualifiedName().name(), + doSerialize( options, + state.namedAssociationFor( association.accessor() ).references() + .collect( toMap() ), + false ) ) ); + if( !root && options.includeTypeInfo() ) + { + withTypeInfo( builder, valueType ); + } + return builder.build(); + } + + private JsonObjectBuilder withTypeInfo( JsonObjectBuilder builder, ValueType valueType ) + { + return builder.add( getTypeInfoPropertyName(), valueType.primaryType().getName() ); + } + + /** + * Map serialization. + * + * {@literal Map<String, ?>} are serialized to a {@literal JsonObject}. + * {@literal Map<?, ?>} are serialized to a {@literal JsonArray} or key/value {@literal JsonObject}s. + * Empty maps are serialized to an empty {@literal JsonObject}. + */ + private JsonValue serializeMap( Options options, Map<?, ?> map ) + { + if( map.isEmpty() ) + { + // Defaults to {} + return Json.createObjectBuilder().build(); + } + if( map.keySet().iterator().next() instanceof CharSequence ) + { + JsonObjectBuilder builder = Json.createObjectBuilder(); + map.entrySet().forEach( entry -> builder.add( entry.getKey().toString(), + doSerialize( options, entry.getValue(), false ) ) ); + return builder.build(); + } + else + { + JsonArrayBuilder builder = Json.createArrayBuilder(); + map.entrySet().forEach( + entry -> builder.add( + Json.createObjectBuilder() + .add( "key", doSerialize( options, entry.getKey(), false ) ) + .add( "value", doSerialize( options, entry.getValue(), false ) ) + .build() ) ); + return builder.build(); + } + } + + private JsonArray serializeIterable( Options options, Iterable<?> iterable ) + { + return serializeStream( options, StreamSupport.stream( iterable.spliterator(), false ) ); + } + + private <T> JsonArray serializeStream( Options options, Stream<?> stream ) + { + JsonArrayBuilder builder = Json.createArrayBuilder(); + stream.forEach( element -> builder.add( doSerialize( options, element, false ) ) ); + return builder.build(); + } + + private JsonString serializeBase64( Object object ) + { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try( ObjectOutputStream out = new ObjectOutputStream( bout ) ) + { + out.writeUnshared( object ); + byte[] bytes = Base64.getEncoder().encode( bout.toByteArray() ); + return JavaxJson.toJsonString( new String( bytes, UTF_8 ) ); + } + catch( IOException ex ) + { + throw new UncheckedIOException( ex ); + } + } + + private String getTypeInfoPropertyName() + { + return JavaxJsonSettings.orDefault( descriptor.metaInfo( JavaxJsonSettings.class ) ) + .getTypeInfoPropertyName(); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSettings.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSettings.java b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSettings.java new file mode 100644 index 0000000..266bd99 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSettings.java @@ -0,0 +1,73 @@ +/* + * 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.polygene.serialization.javaxjson; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.polygene.api.type.ValueType; + +public class JavaxJsonSettings +{ + public static final JavaxJsonSettings DEFAULT = new JavaxJsonSettings(); + + public static JavaxJsonSettings orDefault( JavaxJsonSettings settings ) + { + return settings != null ? settings : DEFAULT; + } + + private String typeInfoPropertyName; + private Map<ValueType, JavaxJsonAdapter<?>> adapters; + + public JavaxJsonSettings() + { + typeInfoPropertyName = "_type"; + adapters = new LinkedHashMap<>(); + } + + public String getTypeInfoPropertyName() + { + return typeInfoPropertyName; + } + + public void setTypeInfoPropertyName( String typeInfoPropertyName ) + { + this.typeInfoPropertyName = typeInfoPropertyName; + } + + public Map<ValueType, JavaxJsonAdapter<?>> getAdapters() + { + return adapters; + } + + public JavaxJsonSettings withTypeInfoPropertyName( String typeInfoPropertyName ) + { + setTypeInfoPropertyName( typeInfoPropertyName ); + return this; + } + + public JavaxJsonSettings withJsonAdapter( ValueType valueType, JavaxJsonAdapter<?> adapter ) + { + getAdapters().put( valueType, adapter ); + return this; + } + + public JavaxJsonSettings withJsonAdapter( JavaxJsonAdapter<?> adapter ) + { + return withJsonAdapter( ValueType.of( adapter.type() ), adapter ); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/package.html ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/package.html b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/package.html new file mode 100644 index 0000000..43db1d9 --- /dev/null +++ b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/package.html @@ -0,0 +1,24 @@ +<!-- + ~ 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. + ~ + ~ + --> +<html> + <body> + <h2>javax.json Serialization.</h2> + </body> +</html> http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java index 6bbef04..e4b5c05 100644 --- a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java +++ b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java @@ -14,28 +14,32 @@ * 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.polygene.spi.entitystore.helpers; import java.time.Instant; +import java.util.Map; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonValue; import org.apache.polygene.api.common.QualifiedName; import org.apache.polygene.api.entity.EntityDescriptor; import org.apache.polygene.api.entity.EntityReference; import org.apache.polygene.api.property.PropertyDescriptor; +import org.apache.polygene.api.serialization.SerializationException; import org.apache.polygene.api.structure.ModuleDescriptor; import org.apache.polygene.api.type.ValueType; -import org.apache.polygene.api.value.ValueSerialization; -import org.apache.polygene.api.value.ValueSerializationException; +import org.apache.polygene.api.value.ValueDescriptor; +import org.apache.polygene.serialization.javaxjson.JavaxJson; import org.apache.polygene.spi.entity.EntityState; import org.apache.polygene.spi.entity.EntityStatus; import org.apache.polygene.spi.entity.ManyAssociationState; import org.apache.polygene.spi.entity.NamedAssociationState; import org.apache.polygene.spi.entitystore.EntityStoreException; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; +import org.apache.polygene.spi.serialization.JsonSerialization; /** * Standard JSON implementation of EntityState. @@ -43,7 +47,6 @@ import org.json.JSONObject; public final class JSONEntityState implements EntityState { - private static final String[] EMPTY_NAMES = new String[ 0 ]; private static final String[] CLONE_NAMES = { JSONKeys.IDENTITY, @@ -54,27 +57,27 @@ public final class JSONEntityState }; private final ModuleDescriptor module; - private final ValueSerialization valueSerialization; private final String version; private final EntityReference reference; private final EntityDescriptor entityDescriptor; + private final JsonSerialization serialization; private EntityStatus status; private Instant lastModified; - private JSONObject state; + private JsonObject state; /* package */ JSONEntityState( ModuleDescriptor module, - ValueSerialization valueSerialization, + JsonSerialization serialization, String version, Instant lastModified, EntityReference reference, EntityStatus status, EntityDescriptor entityDescriptor, - JSONObject state + JsonObject state ) { this.module = module; - this.valueSerialization = valueSerialization; + this.serialization = serialization; this.version = version; this.lastModified = lastModified; this.reference = reference; @@ -107,22 +110,45 @@ public final class JSONEntityState { try { - Object json = state.getJSONObject( JSONKeys.PROPERTIES ).opt( stateName.name() ); - if( JSONObject.NULL.equals( json ) ) + JsonValue json = state.getJsonObject( JSONKeys.PROPERTIES ).get( stateName.name() ); + if( json == null || JsonValue.NULL.equals( json ) ) { return null; } else { - PropertyDescriptor descriptor = entityDescriptor.state().findPropertyModelByQualifiedName( stateName ); - if( descriptor == null ) + // TODO This rely on _type explicitely :( + // Needed because of this mess that is JsonEntityState + ValueType propertyValueType = null; + if( json.getValueType() == JsonValue.ValueType.OBJECT ) + { + String typeInfo = ( (JsonObject) json ).getString( "_type", null ); + if( typeInfo != null ) + { + ValueDescriptor valueDescriptor = module.valueDescriptor( typeInfo ); + if( valueDescriptor != null ) + { + propertyValueType = valueDescriptor.valueType(); + } + } + } + if( propertyValueType == null ) + { + PropertyDescriptor descriptor = entityDescriptor.state() + .findPropertyModelByQualifiedName( stateName ); + if( descriptor != null ) + { + propertyValueType = descriptor.valueType(); + } + } + if( propertyValueType == null ) { return null; } - return valueSerialization.deserialize( module, descriptor.valueType(), json.toString() ); + return serialization.fromJson( module, propertyValueType, json ); } } - catch( ValueSerializationException | JSONException e ) + catch( SerializationException e ) { throw new EntityStoreException( e ); } @@ -133,129 +159,44 @@ public final class JSONEntityState { try { - Object jsonValue; - if( newValue == null || ValueType.isPrimitiveValue( newValue ) ) - { - jsonValue = newValue; - } - else if( ValueType.isIdentity( newValue ) ) - { - jsonValue = newValue.toString(); - } - else - { - String serialized = valueSerialization.serialize( newValue ); - if( serialized.startsWith( "{" ) ) - { - jsonValue = new JSONObject( serialized ); - } - else if( serialized.startsWith( "[" ) ) - { - jsonValue = new JSONArray( serialized ); - } - else - { - jsonValue = serialized; - } - } - cloneStateIfGlobalStateLoaded(); - state.getJSONObject( JSONKeys.PROPERTIES ).put( stateName.name(), jsonValue ); + JsonValue jsonValue = serialization.toJson( newValue ); + stateCloneWithProperty( stateName.name(), jsonValue ); markUpdated(); } - catch( ValueSerializationException | JSONException e ) + catch( SerializationException e ) { throw new EntityStoreException( "Unable to set property " + stateName + " value " + newValue, e ); } } - private JSONObject cloneJSON( JSONObject jsonObject ) - throws JSONException - { - String[] names = JSONObject.getNames( jsonObject ); - if( names == null ) - { - names = EMPTY_NAMES; - } - return new JSONObject( jsonObject, names ); - } - @Override public EntityReference associationValueOf( QualifiedName stateName ) { - try - { - Object jsonValue = state.getJSONObject( JSONKeys.ASSOCIATIONS ).opt( stateName.name() ); - if( jsonValue == null ) - { - return null; - } - - EntityReference value = jsonValue == JSONObject.NULL - ? null - : EntityReference.parseEntityReference( (String) jsonValue ); - return value; - } - catch( JSONException e ) + String jsonValue = state.getJsonObject( JSONKeys.ASSOCIATIONS ).getString( stateName.name(), null ); + if( jsonValue == null ) { - throw new EntityStoreException( e ); + return null; } + return EntityReference.parseEntityReference( jsonValue ); } @Override public void setAssociationValue( QualifiedName stateName, EntityReference newEntity ) { - try - { - cloneStateIfGlobalStateLoaded(); - state.getJSONObject( JSONKeys.ASSOCIATIONS ).put( stateName.name(), newEntity == null - ? null - : newEntity.identity().toString() ); - markUpdated(); - } - catch( JSONException e ) - { - throw new EntityStoreException( e ); - } + stateCloneWithAssociation( stateName.name(), newEntity ); + markUpdated(); } @Override public ManyAssociationState manyAssociationValueOf( QualifiedName stateName ) { - try - { - JSONObject manyAssociations = state.getJSONObject( JSONKeys.MANY_ASSOCIATIONS ); - JSONArray jsonValues = manyAssociations.optJSONArray( stateName.name() ); - if( jsonValues == null ) - { - jsonValues = new JSONArray(); - manyAssociations.put( stateName.name(), jsonValues ); - } - return new JSONManyAssociationState( this, jsonValues ); - } - catch( JSONException e ) - { - throw new EntityStoreException( e ); - } + return new JSONManyAssociationState( this, stateName.name() ); } @Override public NamedAssociationState namedAssociationValueOf( QualifiedName stateName ) { - try - { - JSONObject namedAssociations = state.getJSONObject( JSONKeys.NAMED_ASSOCIATIONS ); - JSONObject jsonValues = namedAssociations.optJSONObject( stateName.name() ); - if( jsonValues == null ) - { - jsonValues = new JSONObject(); - namedAssociations.put( stateName.name(), jsonValues ); - } - return new JSONNamedAssociationState( this, jsonValues ); - } - catch( JSONException e ) - { - throw new EntityStoreException( e ); - } + return new JSONNamedAssociationState( this, stateName.name() ); } @Override @@ -282,7 +223,7 @@ public final class JSONEntityState return entityDescriptor; } - public JSONObject state() + public JsonObject state() { return state; } @@ -293,7 +234,7 @@ public final class JSONEntityState return reference + "(" + state + ")"; } - public void markUpdated() + void markUpdated() { if( status == EntityStatus.LOADED ) { @@ -301,29 +242,217 @@ public final class JSONEntityState } } - void cloneStateIfGlobalStateLoaded() + void stateCloneWithVersionAndModified( String version, Instant lastModified ) + { + JsonObjectBuilder builder = JavaxJson.toBuilder( state ); + builder.add( JSONKeys.VERSION, version ); + builder.add( JSONKeys.MODIFIED, lastModified.toEpochMilli() ); + state = builder.build(); + } + + void stateCloneWithProperty( String stateName, JsonValue value ) { - if( status != EntityStatus.LOADED ) + JsonObjectBuilder builder = stateShallowClone(); + JsonObjectBuilder propertiesBuilder = JavaxJson.toBuilder( state.getJsonObject( JSONKeys.PROPERTIES ) ); + if( value == null ) + { + propertiesBuilder.add( stateName, JsonValue.NULL ); + } + else { - return; + propertiesBuilder.add( stateName, value ); } + builder.add( JSONKeys.PROPERTIES, propertiesBuilder.build() ); + builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) ); + builder.add( JSONKeys.MANY_ASSOCIATIONS, state.get( JSONKeys.MANY_ASSOCIATIONS ) ); + builder.add( JSONKeys.NAMED_ASSOCIATIONS, state.get( JSONKeys.NAMED_ASSOCIATIONS ) ); + state = builder.build(); + } - try + void stateCloneWithAssociation( String stateName, EntityReference ref ) + { + JsonObjectBuilder builder = stateShallowClone(); + JsonObjectBuilder assocBuilder = JavaxJson.toBuilder( state.getJsonObject( JSONKeys.ASSOCIATIONS ) ); + if( ref == null ) { - JSONObject newProperties = cloneJSON( state.getJSONObject( JSONKeys.PROPERTIES ) ); - JSONObject newAssoc = cloneJSON( state.getJSONObject( JSONKeys.ASSOCIATIONS ) ); - JSONObject newManyAssoc = cloneJSON( state.getJSONObject( JSONKeys.MANY_ASSOCIATIONS ) ); - JSONObject newNamedAssoc = cloneJSON( state.getJSONObject( JSONKeys.NAMED_ASSOCIATIONS ) ); - JSONObject stateClone = new JSONObject( state, CLONE_NAMES ); - stateClone.put( JSONKeys.PROPERTIES, newProperties ); - stateClone.put( JSONKeys.ASSOCIATIONS, newAssoc ); - stateClone.put( JSONKeys.MANY_ASSOCIATIONS, newManyAssoc ); - stateClone.put( JSONKeys.NAMED_ASSOCIATIONS, newNamedAssoc ); - state = stateClone; + assocBuilder.add( stateName, JsonValue.NULL ); } - catch( JSONException e ) + else { - throw new EntityStoreException( e ); + assocBuilder.add( stateName, ref.identity().toString() ); + } + builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) ); + builder.add( JSONKeys.ASSOCIATIONS, assocBuilder.build() ); + builder.add( JSONKeys.MANY_ASSOCIATIONS, state.get( JSONKeys.MANY_ASSOCIATIONS ) ); + builder.add( JSONKeys.NAMED_ASSOCIATIONS, state.get( JSONKeys.NAMED_ASSOCIATIONS ) ); + state = builder.build(); + } + + void stateCloneAddManyAssociation( int idx, String stateName, EntityReference ref ) + { + JsonObjectBuilder builder = stateShallowClone(); + JsonObjectBuilder manyAssociations = Json.createObjectBuilder(); + JsonObject previousManyAssociations = state.getJsonObject( JSONKeys.MANY_ASSOCIATIONS ); + for( Map.Entry<String, JsonValue> previousManyAssociation : previousManyAssociations.entrySet() ) + { + String key = previousManyAssociation.getKey(); + if( !key.equals( stateName ) ) + { + manyAssociations.add( key, previousManyAssociation.getValue() ); + } + } + JsonValue previousReferences = previousManyAssociations.get( stateName ); + JsonArrayBuilder references = Json.createArrayBuilder(); + String newRef = ref.identity().toString(); + if( previousReferences == null || previousReferences.getValueType() != JsonValue.ValueType.ARRAY ) + { + references.add( newRef ); + } + else + { + JsonArray previousReferencesArray = (JsonArray) previousReferences; + boolean insert = !previousReferencesArray.contains( newRef ); + for( int i = 0; i < previousReferencesArray.size(); i++ ) + { + if( insert && i == idx ) + { + references.add( newRef ); + } + references.add( previousReferencesArray.getString( i ) ); + } + if( insert && idx >= previousReferencesArray.size() ) + { + references.add( newRef ); + } + } + manyAssociations.add( stateName, references.build() ); + builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) ); + builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) ); + builder.add( JSONKeys.MANY_ASSOCIATIONS, manyAssociations.build() ); + builder.add( JSONKeys.NAMED_ASSOCIATIONS, state.get( JSONKeys.NAMED_ASSOCIATIONS ) ); + state = builder.build(); + } + + void stateCloneRemoveManyAssociation( String stateName, EntityReference ref ) + { + String stringRef = ref.identity().toString(); + JsonObjectBuilder builder = stateShallowClone(); + JsonObjectBuilder manyAssociations = Json.createObjectBuilder(); + JsonObject previousManyAssociations = state.getJsonObject( JSONKeys.MANY_ASSOCIATIONS ); + for( Map.Entry<String, JsonValue> previousManyAssociation : previousManyAssociations.entrySet() ) + { + String key = previousManyAssociation.getKey(); + if( !key.equals( stateName ) ) + { + manyAssociations.add( key, previousManyAssociation.getValue() ); + } + } + JsonValue previousReferences = previousManyAssociations.get( stateName ); + JsonArrayBuilder references = Json.createArrayBuilder(); + if( previousReferences != null && previousReferences.getValueType() == JsonValue.ValueType.ARRAY ) + { + JsonArray previousReferencesArray = (JsonArray) previousReferences; + for( int idx = 0; idx < previousReferencesArray.size(); idx++ ) + { + String previousRef = previousReferencesArray.getString( idx ); + if( !stringRef.equals( previousRef ) ) + { + references.add( previousRef ); + } + } + } + manyAssociations.add( stateName, references.build() ); + builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) ); + builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) ); + builder.add( JSONKeys.MANY_ASSOCIATIONS, manyAssociations.build() ); + builder.add( JSONKeys.NAMED_ASSOCIATIONS, state.get( JSONKeys.NAMED_ASSOCIATIONS ) ); + state = builder.build(); + } + + void stateCloneAddNamedAssociation( String stateName, String name, EntityReference ref ) + { + JsonObjectBuilder builder = stateShallowClone(); + JsonObject previousNamedAssociations = state.getJsonObject( JSONKeys.NAMED_ASSOCIATIONS ); + JsonObjectBuilder namedAssociations = Json.createObjectBuilder(); + for( Map.Entry<String, JsonValue> previousNamedAssociation : previousNamedAssociations.entrySet() ) + { + String key = previousNamedAssociation.getKey(); + if( !key.equals( stateName ) ) + { + namedAssociations.add( key, previousNamedAssociation.getValue() ); + } + } + JsonValue previousReferences = previousNamedAssociations.get( stateName ); + JsonObjectBuilder references = Json.createObjectBuilder(); + String newRef = ref.identity().toString(); + if( previousReferences == null || !( previousReferences instanceof JsonObject ) ) + { + references.add( name, newRef ); + } + else + { + JsonObject previousReferencesObject = (JsonObject) previousReferences; + for( Map.Entry<String, JsonValue> previousNamedReference : previousReferencesObject.entrySet() ) + { + String key = previousNamedReference.getKey(); + if( !key.equals( name ) ) + { + references.add( key, previousNamedReference.getValue() ); + } + } + references.add( name, ref.identity().toString() ); + } + namedAssociations.add( stateName, references.build() ); + builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) ); + builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) ); + builder.add( JSONKeys.MANY_ASSOCIATIONS, state.get( JSONKeys.MANY_ASSOCIATIONS ) ); + builder.add( JSONKeys.NAMED_ASSOCIATIONS, namedAssociations.build() ); + state = builder.build(); + } + + void stateCloneRemoveNamedAssociation( String stateName, String name ) + { + JsonObjectBuilder builder = stateShallowClone(); + JsonObjectBuilder namedAssociations = Json.createObjectBuilder(); + JsonObject previousNamedAssociations = state.getJsonObject( JSONKeys.NAMED_ASSOCIATIONS ); + for( Map.Entry<String, JsonValue> previousNamedAssociation : previousNamedAssociations.entrySet() ) + { + String key = previousNamedAssociation.getKey(); + if( !key.equals( stateName ) ) + { + namedAssociations.add( key, previousNamedAssociation.getValue() ); + } + } + JsonValue previousReferences = previousNamedAssociations.get( stateName ); + JsonObjectBuilder references = Json.createObjectBuilder(); + if( previousReferences != null && previousReferences.getValueType() == JsonValue.ValueType.OBJECT ) + { + JsonObject previousReferencesObject = (JsonObject) previousReferences; + for( Map.Entry<String, JsonValue> previousNamedRef : previousReferencesObject.entrySet() ) + { + String previousName = previousNamedRef.getKey(); + if( !name.equals( previousName ) ) + { + references.add( previousName, previousNamedRef.getValue() ); + } + } + } + namedAssociations.add( stateName, references.build() ); + builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) ); + builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) ); + builder.add( JSONKeys.MANY_ASSOCIATIONS, state.get( JSONKeys.MANY_ASSOCIATIONS ) ); + builder.add( JSONKeys.NAMED_ASSOCIATIONS, namedAssociations.build() ); + state = builder.build(); + } + + private JsonObjectBuilder stateShallowClone() + { + JsonObjectBuilder builder = Json.createObjectBuilder(); + for( String cloneName : CLONE_NAMES ) + { + JsonValue cloneValue = state.get( cloneName ); + builder.add( cloneName, cloneValue == null ? JsonValue.NULL : cloneValue ); } + return builder; } } http://git-wip-us.apache.org/repos/asf/polygene-java/blob/e4cca11e/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java index b1efbc1..e9b99c4 100644 --- a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java +++ b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java @@ -21,39 +21,52 @@ package org.apache.polygene.spi.entitystore.helpers; import java.util.Iterator; import java.util.NoSuchElementException; -import org.json.JSONArray; -import org.json.JSONException; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonException; +import javax.json.JsonObject; +import javax.json.JsonValue; import org.apache.polygene.api.entity.EntityReference; import org.apache.polygene.spi.entity.ManyAssociationState; import org.apache.polygene.spi.entitystore.EntityStoreException; /** * JSON implementation of ManyAssociationState. - * <p>Backed by a JSONArray.</p> + * <p>Backed by a JsonArray.</p> */ public final class JSONManyAssociationState implements ManyAssociationState { - private final JSONEntityState entityState; - private final JSONArray references; + private final String stateName; - public JSONManyAssociationState( JSONEntityState entityState, JSONArray references ) + /* package */ JSONManyAssociationState( JSONEntityState entityState, String stateName ) { this.entityState = entityState; - this.references = references; + this.stateName = stateName; + } + + private JsonArray getReferences() + { + JsonObject manyAssociations = entityState.state().getJsonObject( JSONKeys.MANY_ASSOCIATIONS ); + JsonValue references = manyAssociations.get( stateName ); + if( references != null && references.getValueType() == JsonValue.ValueType.ARRAY ) + { + return (JsonArray) references; + } + return Json.createArrayBuilder().build(); } @Override public int count() { - return references.length(); + return getReferences().size(); } @Override public boolean contains( EntityReference entityReference ) { - return indexOfReference( entityReference.toString() ) != -1; + return indexOfReference( entityReference.identity().toString() ) != -1; } @Override @@ -65,12 +78,11 @@ public final class JSONManyAssociationState { return false; } - entityState.cloneStateIfGlobalStateLoaded(); - insertReference( idx, entityReference.identity().toString() ); + entityState.stateCloneAddManyAssociation( idx, stateName, entityReference ); entityState.markUpdated(); return true; } - catch( JSONException e ) + catch( JsonException e ) { throw new EntityStoreException( e ); } @@ -82,8 +94,7 @@ public final class JSONManyAssociationState int refIndex = indexOfReference( entityReference.identity().toString() ); if( refIndex != -1 ) { - entityState.cloneStateIfGlobalStateLoaded(); - references.remove( refIndex ); + entityState.stateCloneRemoveManyAssociation( stateName, entityReference ); entityState.markUpdated(); return true; } @@ -93,14 +104,7 @@ public final class JSONManyAssociationState @Override public EntityReference get( int i ) { - try - { - return EntityReference.parseEntityReference( references.getString( i ) ); - } - catch( JSONException e ) - { - throw new EntityStoreException( e ); - } + return EntityReference.parseEntityReference( getReferences().getString( i ) ); } @Override @@ -113,7 +117,7 @@ public final class JSONManyAssociationState @Override public boolean hasNext() { - return idx < references.length(); + return idx < getReferences().size(); } @Override @@ -121,11 +125,11 @@ public final class JSONManyAssociationState { try { - EntityReference ref = EntityReference.parseEntityReference( references.getString( idx ) ); + EntityReference ref = EntityReference.parseEntityReference( getReferences().getString( idx ) ); idx++; return ref; } - catch( JSONException e ) + catch( JsonException e ) { throw new NoSuchElementException(); } @@ -142,49 +146,19 @@ public final class JSONManyAssociationState @Override public String toString() { - return references.toString(); + return getReferences().toString(); } - private int indexOfReference( String enityIdentityAsString ) + private int indexOfReference( String entityIdentityAsString ) { - for( int idx = 0; idx < references.length(); idx++ ) + JsonArray references = getReferences(); + for( int idx = 0; idx < references.size(); idx++ ) { - if( enityIdentityAsString.equals( references.opt( idx ) ) ) + if( entityIdentityAsString.equals( references.getString( idx, null ) ) ) { return idx; } } return -1; } - - private void insertReference( int insert, Object item ) - throws JSONException - { - if( insert < 0 || insert > references.length() ) - { - throw new JSONException( "JSONArray[" + insert + "] is out of bounds." ); - } - if( insert == references.length() ) - { - // append - references.put( item ); - } - else - { - // insert (copy/insert/apply) - JSONArray output = new JSONArray(); - for( int idx = 0; idx < references.length(); idx++ ) - { - if( idx == insert ) - { - output.put( item ); - } - output.put( references.opt( idx ) ); - } - for( int idx = 0; idx < output.length(); idx++ ) - { - references.put( idx, output.opt( idx ) ); - } - } - } }
