Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 50E8F200C59 for ; Sun, 2 Apr 2017 19:47:47 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 4F748160BAA; Sun, 2 Apr 2017 17:47:47 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 9946D160B9E for ; Sun, 2 Apr 2017 19:47:44 +0200 (CEST) Received: (qmail 18167 invoked by uid 500); 2 Apr 2017 17:47:43 -0000 Mailing-List: contact commits-help@polygene.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@polygene.apache.org Delivered-To: mailing list commits@polygene.apache.org Received: (qmail 18038 invoked by uid 99); 2 Apr 2017 17:47:43 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 02 Apr 2017 17:47:43 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 2FE10DFF5A; Sun, 2 Apr 2017 17:47:43 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: paulmerlin@apache.org To: commits@polygene.apache.org Date: Sun, 02 Apr 2017 17:47:47 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [05/52] [abbrv] polygene-java git commit: New (de)serialization API and SPI & new implementations archived-at: Sun, 02 Apr 2017 17:47:47 -0000 http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/JavaxXmlSerializer.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/JavaxXmlSerializer.java b/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/JavaxXmlSerializer.java new file mode 100644 index 0000000..bf26f0c --- /dev/null +++ b/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/JavaxXmlSerializer.java @@ -0,0 +1,271 @@ +/* + * 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.javaxxml; + +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.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +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.entity.EntityReference; +import org.apache.polygene.api.injection.scope.This; +import org.apache.polygene.api.injection.scope.Uses; +import org.apache.polygene.api.serialization.SerializationException; +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.value.ValueComposite; +import org.apache.polygene.api.value.ValueDescriptor; +import org.apache.polygene.spi.serialization.AbstractTextSerializer; +import org.apache.polygene.spi.serialization.XmlSerializer; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.polygene.api.util.Collectors.toMap; + +/** + * XML Serializer. + */ +public class JavaxXmlSerializer extends AbstractTextSerializer implements XmlSerializer +{ + private static final String NULL_ELEMENT_NAME = "null"; + + @This + private JavaxXmlAdapters adapters; + + @Uses + private ServiceDescriptor descriptor; + + @Override + public Function toXmlFunction( Options options ) + { + return object -> doSerializeRoot( options, object ); + } + + private Document doSerializeRoot( Options options, T object ) + { + try + { + Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + doc.setXmlVersion( "1.1" ); + doc.setXmlStandalone( true ); + Element stateElement = doc.createElement( getSettings().getRootTagName() ); + Node node = doSerialize( doc, options, object, true ); + stateElement.appendChild( node ); + doc.appendChild( stateElement ); + return doc; + } + catch( ParserConfigurationException ex ) + { + throw new SerializationException( "Unable to create XML document. " + + "Is your javax.xml subsystem correctly set up?", ex ); + } + } + + private Node doSerialize( Document document, Options options, T object, boolean root ) + { + if( object == null ) + { + return document.createElement( NULL_ELEMENT_NAME ); + } + Class objectClass = object.getClass(); + JavaxXmlAdapter adapter = adapters.adapterFor( objectClass ); + if( adapter != null ) + { + return adapter.serialize( document, object, value -> doSerialize( document, options, value, false ) ); + } + if( EnumType.isEnum( objectClass ) ) + { + return document.createTextNode( object.toString() ); + } + if( ValueCompositeType.isValueComposite( objectClass ) ) + { + return serializeValueComposite( document, options, object, root ); + } + if( MapType.isMap( objectClass ) ) + { + return serializeMap( document, options, (Map) object ); + } + if( Iterable.class.isAssignableFrom( objectClass ) ) + { + return serializeIterable( document, options, (Iterable) object ); + } + if( Stream.class.isAssignableFrom( objectClass ) ) + { + return serializeStream( document, options, (Stream) object ); + } + // Fallback to Java Serialization in Base 64 + // Include all arrays! + return serializeBase64( document, object ); + } + + private Node serializeValueComposite( Document document, Options options, T 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(); + + Element valueElement = document.createElement( getSettings().getValueTagName() ); + valueType.properties().forEach( + property -> + { + Object value = state.propertyFor( property.accessor() ).get(); + Element element = document.createElement( property.qualifiedName().name() ); + element.appendChild( doSerialize( document, options, value, false ) ); + valueElement.appendChild( element ); + } ); + valueType.associations().forEach( + association -> + { + EntityReference value = state.associationFor( association.accessor() ).reference(); + Element element = document.createElement( association.qualifiedName().name() ); + element.appendChild( doSerialize( document, options, value, false ) ); + valueElement.appendChild( element ); + } + ); + valueType.manyAssociations().forEach( + association -> + { + Stream value = state.manyAssociationFor( association.accessor() ).references(); + Element element = document.createElement( association.qualifiedName().name() ); + element.appendChild( doSerialize( document, options, value, false ) ); + valueElement.appendChild( element ); + } + ); + valueType.namedAssociations().forEach( + association -> + { + Map value = state.namedAssociationFor( association.accessor() ).references() + .collect( toMap() ); + Element element = document.createElement( association.qualifiedName().name() ); + element.appendChild( doSerialize( document, options, value, false ) ); + valueElement.appendChild( element ); + } + ); + if( !root && options.includeTypeInfo() ) + { + valueElement.setAttribute( getSettings().getTypeInfoTagName(), valueType.primaryType().getName() ); + } + return valueElement; + } + + private Node serializeMap( Document document, Options options, Map map ) + { + JavaxXmlSettings settings = getSettings(); + Element mapElement = document.createElement( settings.getMapTagName() ); + if( map.isEmpty() ) + { + return mapElement; + } + Function complexMapping = entry -> + { + Element entryElement = document.createElement( settings.getMapEntryTagName() ); + + Element keyElement = document.createElement( "key" ); + keyElement.appendChild( doSerialize( document, options, entry.getKey(), false ) ); + entryElement.appendChild( keyElement ); + + Element valueElement = document.createElement( "value" ); + valueElement.appendChild( doSerialize( document, options, entry.getValue(), false ) ); + entryElement.appendChild( valueElement ); + + return entryElement; + }; + + if( map.keySet().iterator().next() instanceof CharSequence ) + { + map.entrySet().stream() + .map( entry -> + { + try + { + Element element = document.createElement( entry.getKey().toString() ); + element.appendChild( doSerialize( document, options, entry.getValue(), false ) ); + return element; + } + catch( DOMException ex ) + { + // The key name cannot be encoded as a tag name, fallback to complex mapping + // Tag names cannot start with a digit, some characters cannot be escaped etc... + return complexMapping.apply( entry ); + } + } ) + .forEach( mapElement::appendChild ); + } + else + { + map.entrySet().stream() + .map( complexMapping ) + .forEach( mapElement::appendChild ); + } + return mapElement; + } + + private Node serializeIterable( Document document, Options options, Iterable object ) + { + return serializeStream( document, options, StreamSupport.stream( object.spliterator(), false ) ); + } + + private Node serializeStream( Document document, Options options, Stream object ) + { + JavaxXmlSettings settings = getSettings(); + Element collectionElement = document.createElement( settings.getCollectionTagName() ); + object.map( each -> doSerialize( document, options, each, false ) ) + .forEach( itemValueNode -> + { + Element itemElement = document.createElement( settings.getCollectionElementTagName() ); + itemElement.appendChild( itemValueNode ); + collectionElement.appendChild( itemElement ); + } ); + return collectionElement; + } + + private Node serializeBase64( Document document, T object ) + { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try( ObjectOutputStream out = new ObjectOutputStream( bout ) ) + { + out.writeUnshared( object ); + byte[] bytes = Base64.getEncoder().encode( bout.toByteArray() ); + return document.createCDATASection( new String( bytes, UTF_8 ) ); + } + catch( IOException ex ) + { + throw new UncheckedIOException( ex ); + } + } + + private JavaxXmlSettings getSettings() + { + return JavaxXmlSettings.orDefault( descriptor.metaInfo( JavaxXmlSettings.class ) ); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/JavaxXmlSettings.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/JavaxXmlSettings.java b/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/JavaxXmlSettings.java new file mode 100644 index 0000000..b5c5702 --- /dev/null +++ b/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/JavaxXmlSettings.java @@ -0,0 +1,134 @@ +/* + * 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.javaxxml; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.polygene.api.type.ValueType; + +/** + * javax.xml settings. + * + * Must be registered as meta-info at assembly time. + */ +// TODO javax.xml properties +public class JavaxXmlSettings +{ + public static final JavaxXmlSettings DEFAULT = new JavaxXmlSettings(); + + public static JavaxXmlSettings orDefault( JavaxXmlSettings settings ) + { + return settings != null ? settings : DEFAULT; + } + + private String rootTagName; + private String collectionTagName; + private String collectionElementTagName; + private String mapTagName; + private String mapEntryTagName; + private String valueTagName; + private String typeInfoTagName; + private Map> adapters; + + public JavaxXmlSettings() + { + rootTagName = "state"; + collectionTagName = "collection"; + collectionElementTagName = "element"; + mapTagName = "map"; + mapEntryTagName = "entry"; + valueTagName = "value"; + typeInfoTagName = "_type"; + adapters = new LinkedHashMap<>(); + } + + public String getRootTagName() + { + return rootTagName; + } + + public void setRootTagName( final String rootTagName ) + { + this.rootTagName = rootTagName; + } + + public String getCollectionTagName() + { + return collectionTagName; + } + + public void setCollectionTagName( final String collectionTagName ) + { + this.collectionTagName = collectionTagName; + } + + public String getCollectionElementTagName() + { + return collectionElementTagName; + } + + public void setCollectionElementTagName( final String collectionElementTagName ) + { + this.collectionElementTagName = collectionElementTagName; + } + + public String getMapTagName() + { + return mapTagName; + } + + public void setMapTagName( final String mapTagName ) + { + this.mapTagName = mapTagName; + } + + public String getMapEntryTagName() + { + return mapEntryTagName; + } + + public void setMapEntryTagName( final String mapEntryTagName ) + { + this.mapEntryTagName = mapEntryTagName; + } + + public String getValueTagName() + { + return valueTagName; + } + + public void setValueTagName( final String valueTagName ) + { + this.valueTagName = valueTagName; + } + + public String getTypeInfoTagName() + { + return typeInfoTagName; + } + + public void setTypeInfoTagName( final String typeInfoTagName ) + { + this.typeInfoTagName = typeInfoTagName; + } + + public Map> getAdapters() + { + return adapters; + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/package.html ---------------------------------------------------------------------- diff --git a/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/package.html b/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/package.html new file mode 100644 index 0000000..f81a030 --- /dev/null +++ b/extensions/serialization-javaxxml/src/main/java/org/apache/polygene/serialization/javaxxml/package.html @@ -0,0 +1,24 @@ + + + +

javax.xml Serialization.

+ + http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlAdaptersTest.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlAdaptersTest.java b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlAdaptersTest.java new file mode 100644 index 0000000..16a2fb3 --- /dev/null +++ b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlAdaptersTest.java @@ -0,0 +1,39 @@ +package org.apache.polygene.serialization.javaxxml; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.polygene.api.injection.scope.Service; +import org.apache.polygene.bootstrap.ModuleAssembly; +import org.apache.polygene.test.AbstractPolygeneTest; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +public class JavaxXmlAdaptersTest extends AbstractPolygeneTest +{ + @Override + public void assemble( ModuleAssembly module ) + { + new JavaxXmlSerializationAssembler().assemble( module ); + module.services( JavaxXmlSerializationService.class ) + .withTypes( JavaxXmlAdapters.class ); + } + + @Service + private JavaxXmlAdapters adapters; + + @Test + public void test() throws ParserConfigurationException + { + JavaxXmlAdapter adapter = adapters.adapterFor( String.class ); + Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + String original = "Cou€ôÙÔ#‰¥Ô"; + Node node = adapter.serialize( doc, original, null ); + assertThat( node.getNodeValue(), equalTo( original ) ); + String result = adapter.deserialize( node, null ); + assertThat( result, equalTo( original ) ); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlCollectionTest.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlCollectionTest.java b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlCollectionTest.java new file mode 100644 index 0000000..5f0bf3c --- /dev/null +++ b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlCollectionTest.java @@ -0,0 +1,31 @@ +/* + * 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.javaxxml; + +import org.apache.polygene.bootstrap.ModuleAssembly; +import org.apache.polygene.test.serialization.AbstractCollectionSerializationTest; + +public class JavaxXmlCollectionTest extends AbstractCollectionSerializationTest +{ + @Override + public void assemble( ModuleAssembly module ) + { + new JavaxXmlSerializationAssembler().assemble( module ); + super.assemble( module ); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlDateFormatTest.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlDateFormatTest.java b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlDateFormatTest.java new file mode 100644 index 0000000..6f69bbe --- /dev/null +++ b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlDateFormatTest.java @@ -0,0 +1,33 @@ +/* + * 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.javaxxml; + +import org.apache.polygene.bootstrap.ModuleAssembly; +import org.apache.polygene.test.serialization.AbstractDateFormatSerializationTest; +import org.junit.Ignore; + +@Ignore( "Super test assume JSON" ) +public class JavaxXmlDateFormatTest extends AbstractDateFormatSerializationTest +{ + @Override + public void assemble( ModuleAssembly module ) + { + new JavaxXmlSerializationAssembler().assemble( module ); + super.assemble( module ); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlPlainValueTest.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlPlainValueTest.java b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlPlainValueTest.java new file mode 100644 index 0000000..51d8e8a --- /dev/null +++ b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlPlainValueTest.java @@ -0,0 +1,35 @@ +/* + * 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.javaxxml; + +import org.apache.polygene.bootstrap.ModuleAssembly; +import org.apache.polygene.test.serialization.AbstractPlainValueSerializationTest; +import org.junit.Ignore; + +@Ignore( "Super test assume JSON" ) +public class JavaxXmlPlainValueTest extends AbstractPlainValueSerializationTest +{ + @Override + public void assemble( ModuleAssembly module ) + { + new JavaxXmlSerializationAssembler().assemble( module ); + super.assemble( module ); + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlValueCompositeTest.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlValueCompositeTest.java b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlValueCompositeTest.java new file mode 100644 index 0000000..59d0e3e --- /dev/null +++ b/extensions/serialization-javaxxml/src/test/java/org/apache/polygene/serialization/javaxxml/JavaxXmlValueCompositeTest.java @@ -0,0 +1,68 @@ +/* + * 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.javaxxml; + +import org.apache.polygene.api.injection.scope.Service; +import org.apache.polygene.api.unitofwork.UnitOfWork; +import org.apache.polygene.bootstrap.ModuleAssembly; +import org.apache.polygene.spi.serialization.XmlSerialization; +import org.apache.polygene.test.serialization.AbstractValueCompositeSerializationTest; +import org.junit.Test; +import org.xmlunit.diff.DefaultNodeMatcher; +import org.xmlunit.diff.ElementSelectors; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.xmlunit.matchers.CompareMatcher.isSimilarTo; + +public class JavaxXmlValueCompositeTest extends AbstractValueCompositeSerializationTest +{ + @Override + public void assemble( ModuleAssembly module ) + { + new JavaxXmlSerializationAssembler().assemble( module ); + super.assemble( module ); + } + + @Service + XmlSerialization xmlSerialization; + + @Test + public void valueCompositeXmlEquality() + { + try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork() ) + { + Some some = buildSomeValue( moduleInstance, uow, "23" ); + + // Serialize using injected service + String stateString = serialization.serialize( some ); + System.out.println( stateString ); + + // Deserialize using Module API + Some some2 = moduleInstance.newValueFromSerializedState( Some.class, stateString ); + + assertThat( "Value equality", some, equalTo( some2 ) ); + + // Need to loosely compare because of HashMaps not retaining order + assertThat( "XML equality", + stateString, + isSimilarTo( some2.toString() ) + .withNodeMatcher( new DefaultNodeMatcher( ElementSelectors.byNameAndAllAttributes ) ) ); + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/build.gradle ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/build.gradle b/extensions/serialization-msgpack/build.gradle new file mode 100644 index 0000000..6f51948 --- /dev/null +++ b/extensions/serialization-msgpack/build.gradle @@ -0,0 +1,36 @@ +/* + * 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. + */ + +apply plugin: 'polygene-extension' + +description = "Apache Polygene™ MessagePack Serialization Extension" + +jar { manifest { name = "Apache Polygene™ Extension - Serialization - MessagePack" } } + +dependencies { + api polygene.core.bootstrap + api libraries.msgpack + + implementation libraries.commons_lang + + runtimeOnly polygene.core.runtime + + testImplementation polygene.core.testsupport + + testRuntimeOnly libraries.logback +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/dev-status.xml ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/dev-status.xml b/extensions/serialization-msgpack/dev-status.xml new file mode 100644 index 0000000..8086fb0 --- /dev/null +++ b/extensions/serialization-msgpack/dev-status.xml @@ -0,0 +1,38 @@ + + + + + + early + + + none + + + some + + + ALv2 + + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/src/docs/serialization-msgpack.txt ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/src/docs/serialization-msgpack.txt b/extensions/serialization-msgpack/src/docs/serialization-msgpack.txt new file mode 100644 index 0000000..ad50c08 --- /dev/null +++ b/extensions/serialization-msgpack/src/docs/serialization-msgpack.txt @@ -0,0 +1,30 @@ +/////////////////////////////////////////////////////////////// + * 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. +/////////////////////////////////////////////////////////////// + +[[extension-serialization-msgpack,MessagePack serialization]] += MessagePack serialization = + +[devstatus] +-------------- +source=extensions/serialization-msgpack/dev-status.xml +-------------- + +// TODO Document usage of MessagePackSerialization +// TODO Include sample model and its output from test code & resources +// TODO Assembly - Serialization extension or sole Service, settings & adapters http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackAdapter.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackAdapter.java b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackAdapter.java new file mode 100644 index 0000000..6d99e69 --- /dev/null +++ b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackAdapter.java @@ -0,0 +1,57 @@ +/* + * 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.msgpack; + +import java.io.IOException; +import java.util.function.BiFunction; +import java.util.function.Function; +import org.apache.polygene.api.type.ValueType; +import org.msgpack.value.Value; + +/** + * Adapter for MessagePack (de)serialization. + * + * @param the adapted type + */ +public interface MessagePackAdapter +{ + /** + * @return the adapted type + */ + Class type(); + + /** + * Serialize. + * + * @param object Object to serialize, never null + * @param serializeFunction Serialization function for nested structure serialization + * @return MessagePack Value + */ + Value serialize( Object object, Function serializeFunction ) + throws IOException; + + /** + * Deserialize. + * + * @param value MessagePack value + * @param deserializeFunction Deserialization function for nested structure deserialization + * @return Deserialized object + */ + T deserialize( Value value, BiFunction deserializeFunction ) + throws IOException; +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackAdapters.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackAdapters.java b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackAdapters.java new file mode 100644 index 0000000..892b389 --- /dev/null +++ b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackAdapters.java @@ -0,0 +1,64 @@ +/* + * 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.msgpack; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.polygene.api.mixin.Mixins; +import org.apache.polygene.api.type.ValueType; + +import static org.apache.polygene.api.type.HasTypesCollectors.closestType; + +@Mixins( MessagePackAdapters.Mixin.class ) +public interface MessagePackAdapters +{ + void registerAdapter( ValueType valueType, MessagePackAdapter adapter ); + + MessagePackAdapter adapterFor( ValueType valueType ); + + default MessagePackAdapter adapterFor( Class type ) + { + return adapterFor( ValueType.of( type ) ); + } + + class Mixin implements MessagePackAdapters + { + private Map> adapters = new LinkedHashMap<>(); + + @Override + public void registerAdapter( ValueType valueType, MessagePackAdapter adapter ) + { + adapters.put( valueType, adapter ); + } + + @Override + public MessagePackAdapter adapterFor( final ValueType valueType ) + { + return castAdapter( adapters.keySet().stream() + .collect( closestType( valueType ) ) + .map( adapters::get ) + .orElse( null ) ); + } + + @SuppressWarnings( "unchecked" ) + private MessagePackAdapter castAdapter( MessagePackAdapter adapter ) + { + return (MessagePackAdapter) adapter; + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackDeserializer.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackDeserializer.java b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackDeserializer.java new file mode 100644 index 0000000..84508a4 --- /dev/null +++ b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackDeserializer.java @@ -0,0 +1,295 @@ +/* + * 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.msgpack; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; +import org.apache.polygene.api.association.AssociationDescriptor; +import org.apache.polygene.api.entity.EntityReference; +import org.apache.polygene.api.injection.scope.This; +import org.apache.polygene.api.mixin.Mixins; +import org.apache.polygene.api.property.PropertyDescriptor; +import org.apache.polygene.api.serialization.Deserializer; +import org.apache.polygene.api.serialization.SerializationException; +import org.apache.polygene.api.structure.ModuleDescriptor; +import org.apache.polygene.api.type.CollectionType; +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.ValueBuilder; +import org.apache.polygene.api.value.ValueDescriptor; +import org.apache.polygene.spi.serialization.AbstractBinaryDeserializer; +import org.msgpack.core.MessagePack; +import org.msgpack.core.MessageUnpacker; +import org.msgpack.value.ArrayValue; +import org.msgpack.value.BinaryValue; +import org.msgpack.value.ImmutableValue; +import org.msgpack.value.MapValue; +import org.msgpack.value.Value; + +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableMap; +import static java.util.Collections.unmodifiableSet; +import static org.apache.polygene.api.util.Collectors.toMap; + +// TODO Test all deserializations for: missing & spurious entries +@Mixins( MessagePackDeserializer.Mixin.class ) +public interface MessagePackDeserializer extends Deserializer +{ + class Mixin extends AbstractBinaryDeserializer + { + @This + private MessagePackAdapters adapters; + + @Override + public T deserialize( ModuleDescriptor module, ValueType valueType, InputStream state ) + { + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker( state ); + try + { + if( !unpacker.hasNext() ) + { + return null; + } + ImmutableValue value = unpacker.unpackValue(); + return doDeserialize( module, valueType, value ); + } + catch( IOException e ) + { + throw new SerializationException( "Unable to deserialize " + valueType ); + } + } + + @SuppressWarnings( "unchecked" ) + private T doDeserialize( ModuleDescriptor module, ValueType valueType, Value value ) + { + try + { + if( value.isNilValue() ) + { + return null; + } + MessagePackAdapter adapter = adapters.adapterFor( valueType ); + if( adapter != null ) + { + return (T) adapter.deserialize( value, ( val, type ) -> doDeserialize( module, valueType, val ) ); + } + if( EnumType.class.isAssignableFrom( valueType.getClass() ) ) + { + return (T) Enum.valueOf( (Class) valueType.primaryType(), value.asStringValue().asString() ); + } + if( CollectionType.class.isAssignableFrom( valueType.getClass() ) ) + { + return (T) deserializeCollection( module, (CollectionType) valueType, value.asArrayValue() ); + } + if( MapType.class.isAssignableFrom( valueType.getClass() ) ) + { + return (T) deserializeMap( module, (MapType) valueType, value.asMapValue() ); + } + if( ValueCompositeType.class.isAssignableFrom( valueType.getClass() ) ) + { + return (T) deserializeValueComposite( module, (ValueCompositeType) valueType, value.asMapValue() ); + } + return (T) doGuessDeserialize( module, valueType, value ); + } + catch( IOException | ClassNotFoundException ex ) + { + throw new SerializationException( "Unable to deserialize " + valueType + " from: " + value ); + } + } + + private Collection deserializeCollection( ModuleDescriptor module, CollectionType collectionType, + ArrayValue value ) throws IOException + { + Collection collection = collectionType.isSet() ? new LinkedHashSet( value.size() ) + : new ArrayList( value.size() ); + for( Value element : value.list() ) + { + collection.add( doDeserialize( module, collectionType.collectedType(), element ) ); + } + return collection; + } + + private Map deserializeMap( ModuleDescriptor module, MapType mapType, MapValue value ) + throws IOException + { + Map map = new LinkedHashMap<>( value.size() ); + for( Map.Entry entry : value.entrySet() ) + { + Object key = doDeserialize( module, mapType.keyType(), entry.getKey() ); + Object val = doDeserialize( module, mapType.valueType(), entry.getValue() ); + map.put( key, val ); + } + return map; + } + + private Object deserializeValueComposite( ModuleDescriptor module, ValueCompositeType valueType, + MapValue value ) throws IOException + { + Map namedValues = value.map().entrySet().stream().map( + entry -> + { + String key = doDeserialize( module, ValueType.STRING, entry.getKey() ); + return new AbstractMap.SimpleImmutableEntry<>( key, entry.getValue() ); + } + ).collect( toMap( HashMap::new ) ); + + String typeInfo = null; + if( namedValues.containsKey( "_type" ) ) + { + typeInfo = doDeserialize( module, ValueType.STRING, namedValues.get( "_type" ) ); + } + if( typeInfo != null ) + { + ValueDescriptor descriptor = module.valueDescriptor( typeInfo ); + if( descriptor == null ) + { + throw new SerializationException( + "_type: " + typeInfo + " could not be resolved while deserializing " + value ); + } + valueType = descriptor.valueType(); + } + + ValueBuilder builder = module.instance().newValueBuilderWithState( + valueType.primaryType(), + propertyFunction( module, namedValues ), + associationFunction( module, namedValues ), + manyAssociationFunction( module, namedValues ), + namedAssociationFunction( module, namedValues ) ); + return builder.newInstance(); + } + + private Function propertyFunction( ModuleDescriptor module, + Map namedValues ) + { + return property -> + { + Value value = namedValues.get( property.qualifiedName().name() ); + if( value != null ) + { + Object propertyValue = doDeserialize( module, property.valueType(), value ); + if( property.isImmutable() ) + { + if( propertyValue instanceof Set ) + { + return unmodifiableSet( (Set) propertyValue ); + } + else if( propertyValue instanceof List ) + { + return unmodifiableList( (List) propertyValue ); + } + else if( propertyValue instanceof Map ) + { + return unmodifiableMap( (Map) propertyValue ); + } + } + return propertyValue; + } + return property.resolveInitialValue( module ); + }; + } + + private Function associationFunction( ModuleDescriptor module, + Map namedValues ) + { + return association -> doDeserialize( module, ValueType.ENTITY_REFERENCE, + namedValues.get( association.qualifiedName().name() ) ); + } + + private Function> manyAssociationFunction( + ModuleDescriptor module, Map namedValues ) + { + return association -> + { + List list = doDeserialize( module, ENTITY_REF_LIST_VALUE_TYPE, + namedValues.get( association.qualifiedName().name() ) ); + return list == null ? Stream.empty() : list.stream(); + }; + } + + private Function>> namedAssociationFunction( + ModuleDescriptor module, Map namedValues ) + { + return association -> + { + Map map = doDeserialize( module, ENTITY_REF_MAP_VALUE_TYPE, + namedValues.get( association.qualifiedName().name() ) ); + return map == null ? Stream.empty() : map.entrySet().stream(); + }; + } + + private Object doGuessDeserialize( ModuleDescriptor module, ValueType valueType, Value value ) + throws IOException, ClassNotFoundException + { + switch( value.getValueType() ) + { + case BINARY: + return deserializeJava( value.asBinaryValue() ); + case MAP: + MapValue mapValue = value.asMapValue(); + Optional typeInfo = mapValue + .entrySet().stream() + .filter( entry -> entry.getKey().isStringValue() ) + .map( entry -> + { + String key = doDeserialize( module, ValueType.STRING, entry.getKey() ); + return new AbstractMap.SimpleImmutableEntry<>( key, entry.getValue() ); + } ) + .filter( entry -> "_type".equals( entry.getKey() ) ) + .findFirst() + .map( entry -> doDeserialize( module, ValueType.STRING, entry.getValue() ) ); + if( typeInfo.isPresent() ) + { + ValueDescriptor valueDescriptor = module.valueDescriptor( typeInfo.get() ); + if( valueDescriptor != null ) + { + return deserializeValueComposite( module, valueDescriptor.valueType(), mapValue ); + } + } + default: + throw new SerializationException( "Don't know how to deserialize " + valueType + " from " + value + + " (" + value.getValueType() + ")" ); + } + } + + private Object deserializeJava( BinaryValue value ) + throws IOException, ClassNotFoundException + { + byte[] bytes = value.asByteArray(); + try( ObjectInputStream oin = new ObjectInputStream( new ByteArrayInputStream( bytes ) ) ) + { + return oin.readObject(); + } + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerialization.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerialization.java b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerialization.java new file mode 100644 index 0000000..d24d597 --- /dev/null +++ b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerialization.java @@ -0,0 +1,22 @@ +/* + * 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.msgpack; + +public interface MessagePackSerialization extends MessagePackSerializer, MessagePackDeserializer +{ +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializationAssembler.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializationAssembler.java b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializationAssembler.java new file mode 100644 index 0000000..63536cc --- /dev/null +++ b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializationAssembler.java @@ -0,0 +1,52 @@ +/* + * 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.msgpack; + +import org.apache.polygene.api.serialization.Deserializer; +import org.apache.polygene.api.serialization.Serialization; +import org.apache.polygene.api.serialization.Serializer; +import org.apache.polygene.bootstrap.Assemblers; +import org.apache.polygene.bootstrap.ModuleAssembly; +import org.apache.polygene.bootstrap.ServiceDeclaration; + +public class MessagePackSerializationAssembler extends Assemblers.VisibilityIdentity +{ + private MessagePackSettings settings; + + public MessagePackSerializationAssembler withMessagePackSettings( MessagePackSettings settings ) + { + this.settings = settings; + return this; + } + + @Override + public void assemble( ModuleAssembly module ) + { + ServiceDeclaration declaration = module.services( MessagePackSerializationService.class ) + .withTypes( Serialization.class, Serializer.class, Deserializer.class ) + .visibleIn( visibility() ); + if( hasIdentity() ) + { + declaration.identifiedBy( identity() ); + } + if( settings != null ) + { + declaration.setMetaInfo( settings ); + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializationService.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializationService.java b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializationService.java new file mode 100644 index 0000000..4cd23b7 --- /dev/null +++ b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializationService.java @@ -0,0 +1,414 @@ +/* + * 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.msgpack; + +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 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.service.ServiceActivation; +import org.apache.polygene.api.service.ServiceDescriptor; +import org.apache.polygene.api.type.ValueType; +import org.msgpack.value.Value; +import org.msgpack.value.ValueFactory; + +@Mixins( MessagePackSerializationService.Activation.class ) +public interface MessagePackSerializationService extends MessagePackSerialization, ServiceActivation +{ + class Activation implements ServiceActivation + { + @Uses + private ServiceDescriptor descriptor; + + @This + private MessagePackAdapters adapters; + + private boolean registrationDone = false; + + @Override + public void activateService() + { + if( !registrationDone ) + { + registerCustomAdapters(); + registerBaseAdapters(); + registrationDone = true; + } + } + + @Override + public void passivateService() {} + + private void registerCustomAdapters() + { + MessagePackSettings.orDefault( descriptor.metaInfo( MessagePackSettings.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 ToStringAdapter implements MessagePackAdapter + { + @Override + public Value serialize( Object object, Function serializeFunction ) + { + return ValueFactory.newString( object.toString() ); + } + } + + private static class StringAdapter extends ToStringAdapter + { + @Override + public Class type() { return String.class; } + + @Override + public String deserialize( Value value, BiFunction deserializeFunction ) + { + return value.asStringValue().asString(); + } + } + + private static class CharacterAdapter extends ToStringAdapter + { + @Override + public Class type() { return Character.class; } + + @Override + public Character deserialize( Value value, BiFunction deserializeFunction ) + { + String string = value.asStringValue().asString(); + return string.isEmpty() ? null : string.charAt( 0 ); + } + } + + private static class BooleanAdapter implements MessagePackAdapter + { + @Override + public Class type() { return Boolean.class; } + + @Override + public Value serialize( Object object, Function serializeFunction ) + { + return ValueFactory.newBoolean( (Boolean) object ); + } + + @Override + public Boolean deserialize( Value value, BiFunction deserializeFunction ) + { + return value.asBooleanValue().getBoolean(); + } + } + + private static class IntegerAdapter implements MessagePackAdapter + { + @Override + public Class type() { return Integer.class; } + + @Override + public Value serialize( Object object, Function serializeFunction ) + { + return ValueFactory.newInteger( (Integer) object ); + } + + @Override + public Integer deserialize( Value value, BiFunction deserializeFunction ) + { + return value.asIntegerValue().asInt(); + } + } + + private static class LongAdapter implements MessagePackAdapter + { + @Override + public Class type() { return Long.class; } + + @Override + public Value serialize( Object object, Function serializeFunction ) + { + return ValueFactory.newInteger( (Long) object ); + } + + @Override + public Long deserialize( Value value, BiFunction deserializeFunction ) + { + return value.asIntegerValue().asLong(); + } + } + + private static class ShortAdapter implements MessagePackAdapter + { + @Override + public Class type() { return Short.class; } + + @Override + public Value serialize( Object object, Function serializeFunction ) + { + return ValueFactory.newInteger( (Short) object ); + } + + @Override + public Short deserialize( Value value, BiFunction deserializeFunction ) + { + return value.asIntegerValue().asShort(); + } + } + + private static class ByteAdapter implements MessagePackAdapter + { + @Override + public Class type() { return Byte.class; } + + @Override + public Value serialize( Object object, Function serializeFunction ) + { + return ValueFactory.newInteger( (Byte) object ); + } + + @Override + public Byte deserialize( Value value, BiFunction deserializeFunction ) + { + return value.asIntegerValue().asByte(); + } + } + + private static class FloatAdapter implements MessagePackAdapter + { + @Override + public Class type() { return Float.class; } + + @Override + public Value serialize( Object object, Function serializeFunction ) + { + return ValueFactory.newFloat( (Float) object ); + } + + @Override + public Float deserialize( Value value, BiFunction deserializeFunction ) + { + return value.asFloatValue().toFloat(); + } + } + + private static class DoubleAdapter implements MessagePackAdapter + { + @Override + public Class type() { return Double.class; } + + @Override + public Value serialize( Object object, Function serializeFunction ) + { + return ValueFactory.newFloat( (Double) object ); + } + + @Override + public Double deserialize( Value value, BiFunction deserializeFunction ) + { + return value.asFloatValue().toDouble(); + } + } + + private static class BigDecimalAdapter extends ToStringAdapter + { + @Override + public Class type() { return BigDecimal.class; } + + @Override + public BigDecimal deserialize( Value value, BiFunction deserializeFunction ) + { + return new BigDecimal( value.asStringValue().asString() ); + } + } + + private static class BigIntegerAdapter extends ToStringAdapter + { + @Override + public Class type() { return BigInteger.class; } + + @Override + public BigInteger deserialize( Value value, BiFunction deserializeFunction ) + { + return new BigInteger( value.asStringValue().asString() ); + } + } + + private static class InstantAdapter extends ToStringAdapter + { + @Override + public Class type() { return Instant.class; } + + @Override + public Instant deserialize( Value value, BiFunction deserializeFunction ) + { + return Instant.parse( value.asStringValue().asString() ); + } + } + + private static class ZonedDateTimeAdapter extends ToStringAdapter + { + @Override + public Class type() { return ZonedDateTime.class; } + + @Override + public ZonedDateTime deserialize( Value value, BiFunction deserializeFunction ) + { + return ZonedDateTime.parse( value.asStringValue().asString() ); + } + } + + private static class OffsetDateTimeAdapter extends ToStringAdapter + { + @Override + public Class type() { return OffsetDateTime.class; } + + @Override + public OffsetDateTime deserialize( Value value, BiFunction deserializeFunction ) + { + return OffsetDateTime.parse( value.asStringValue().asString() ); + } + } + + private static class LocalDateTimeAdapter extends ToStringAdapter + { + @Override + public Class type() { return LocalDateTime.class; } + + @Override + public LocalDateTime deserialize( Value value, BiFunction deserializeFunction ) + { + return LocalDateTime.parse( value.asStringValue().asString() ); + } + } + + private static class LocalDateAdapter extends ToStringAdapter + { + @Override + public Class type() { return LocalDate.class; } + + @Override + public LocalDate deserialize( Value value, BiFunction deserializeFunction ) + { + return LocalDate.parse( value.asStringValue().asString() ); + } + } + + private static class LocalTimeAdapter extends ToStringAdapter + { + @Override + public Class type() { return LocalTime.class; } + + @Override + public LocalTime deserialize( Value value, BiFunction deserializeFunction ) + { + return LocalTime.parse( value.asStringValue().asString() ); + } + } + + private static class DurationAdapter extends ToStringAdapter + { + @Override + public Class type() { return Duration.class; } + + @Override + public Duration deserialize( Value value, BiFunction deserializeFunction ) + { + return Duration.parse( value.asStringValue().asString() ); + } + } + + private static class PeriodAdapter extends ToStringAdapter + { + @Override + public Class type() { return Period.class; } + + @Override + public Period deserialize( Value value, BiFunction deserializeFunction ) + { + return Period.parse( value.asStringValue().asString() ); + } + } + + private static class IdentityAdapter extends ToStringAdapter + { + @Override + public Class type() { return Identity.class; } + + @Override + public Identity deserialize( Value value, BiFunction deserializeFunction ) + { + return StringIdentity.fromString( value.asStringValue().asString() ); + } + } + + private static class EntityReferenceAdapter extends ToStringAdapter + { + @Override + public Class type() { return EntityReference.class; } + + @Override + public EntityReference deserialize( Value value, BiFunction deserializeFunction ) + { + return EntityReference.parseEntityReference( value.asStringValue().asString() ); + } + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializer.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializer.java b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializer.java new file mode 100644 index 0000000..7321e6d --- /dev/null +++ b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSerializer.java @@ -0,0 +1,187 @@ +/* + * 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.msgpack; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.Map; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.apache.polygene.api.PolygeneAPI; +import org.apache.polygene.api.association.AssociationStateHolder; +import org.apache.polygene.api.common.Optional; +import org.apache.polygene.api.composite.CompositeInstance; +import org.apache.polygene.api.injection.scope.This; +import org.apache.polygene.api.mixin.Mixins; +import org.apache.polygene.api.serialization.SerializationException; +import org.apache.polygene.api.serialization.Serializer; +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.value.ValueComposite; +import org.apache.polygene.api.value.ValueDescriptor; +import org.apache.polygene.spi.serialization.AbstractBinarySerializer; +import org.msgpack.core.MessagePack; +import org.msgpack.core.MessagePacker; +import org.msgpack.value.ArrayValue; +import org.msgpack.value.BinaryValue; +import org.msgpack.value.MapValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueFactory; + +import static java.util.stream.Collectors.toList; +import static org.apache.polygene.api.util.Collectors.toMap; + +@Mixins( MessagePackSerializer.Mixin.class ) +public interface MessagePackSerializer extends Serializer +{ + class Mixin extends AbstractBinarySerializer + { + @This + private MessagePackAdapters adapters; + + @Override + public void serialize( Options options, OutputStream output, @Optional Object object ) + { + MessagePacker packer = MessagePack.newDefaultPacker( output ); + Value value = doSerialize( options, object, true ); + try + { + packer.packValue( value ); + packer.flush(); + } + catch( IOException ex ) + { + throw new SerializationException( "Unable to serialize " + object, ex ); + } + } + + private Value doSerialize( Options options, Object object, boolean root ) + { + try + { + if( object == null ) + { + return ValueFactory.newNil(); + } + Class objectClass = object.getClass(); + MessagePackAdapter adapter = adapters.adapterFor( objectClass ); + if( adapter != null ) + { + return adapter.serialize( object, obj -> doSerialize( options, obj, false ) ); + } + if( EnumType.isEnum( objectClass ) ) + { + return ValueFactory.newString( 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 + // Include all arrays! + return serializeJava( object ); + } + catch( IOException ex ) + { + throw new SerializationException( "Unable to serialize " + object, ex ); + } + } + + private MapValue 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(); + + ValueFactory.MapBuilder builder = ValueFactory.newMapBuilder(); + valueType.properties().forEach( + property -> builder.put( + ValueFactory.newString( property.qualifiedName().name() ), + doSerialize( options, state.propertyFor( property.accessor() ).get(), false ) ) ); + valueType.associations().forEach( + association -> builder.put( + ValueFactory.newString( association.qualifiedName().name() ), + doSerialize( options, state.associationFor( association.accessor() ).reference(), false ) ) ); + valueType.manyAssociations().forEach( + association -> builder.put( + ValueFactory.newString( association.qualifiedName().name() ), + doSerialize( options, + state.manyAssociationFor( association.accessor() ).references().collect( toList() ), + false ) ) ); + valueType.namedAssociations().forEach( + association -> builder.put( + ValueFactory.newString( association.qualifiedName().name() ), + doSerialize( options, + state.namedAssociationFor( association.accessor() ).references().collect( toMap() ), + false ) ) ); + + if( !root && options.includeTypeInfo() ) + { + builder.put( ValueFactory.newString( "_type" ), + ValueFactory.newString( valueType.primaryType().getName() ) ); + } + return builder.build(); + } + + private MapValue serializeMap( Options options, Map map ) + { + ValueFactory.MapBuilder builder = ValueFactory.newMapBuilder(); + map.forEach( ( key, value ) -> builder.put( doSerialize( options, key, false ), + doSerialize( options, value, false ) ) ); + return builder.build(); + } + + private ArrayValue serializeIterable( Options options, Iterable iterable ) + { + return serializeStream( options, StreamSupport.stream( iterable.spliterator(), false ) ); + } + + private ArrayValue serializeStream( Options options, Stream stream ) + { + return ValueFactory.newArray( stream.map( element -> doSerialize( options, element, false ) ) + .collect( toList() ) ); + } + + private BinaryValue serializeJava( Object object ) throws IOException + { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try( ObjectOutputStream out = new ObjectOutputStream( bout ) ) + { + out.writeUnshared( object ); + byte[] bytes = bout.toByteArray(); + return ValueFactory.newBinary( bytes ); + } + } + } +} http://git-wip-us.apache.org/repos/asf/polygene-java/blob/17b11697/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSettings.java ---------------------------------------------------------------------- diff --git a/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSettings.java b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSettings.java new file mode 100644 index 0000000..8080d94 --- /dev/null +++ b/extensions/serialization-msgpack/src/main/java/org/apache/polygene/serialization/msgpack/MessagePackSettings.java @@ -0,0 +1,44 @@ +/* + * 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.msgpack; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.polygene.api.type.ValueType; + +public class MessagePackSettings +{ + public static final MessagePackSettings DEFAULT = new MessagePackSettings(); + + public static MessagePackSettings orDefault( MessagePackSettings settings ) + { + return settings != null ? settings : DEFAULT; + } + + private Map> adapters; + + public MessagePackSettings() + { + adapters = new LinkedHashMap<>(); + } + + public Map> getAdapters() + { + return adapters; + } +}