/*
* Copyright (c) 2010, Niclas Hehdman. All Rights Reserved.
*
* Licensed 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.qi4j.library.cxf;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import org.apache.cxf.aegis.Context;
import org.apache.cxf.aegis.DatabindingException;
import org.apache.cxf.aegis.type.AegisType;
import org.apache.cxf.aegis.type.TypeMapping;
import org.apache.cxf.aegis.type.collection.CollectionType;
import org.apache.cxf.aegis.type.collection.MapType;
import org.apache.cxf.aegis.xml.MessageReader;
import org.apache.cxf.aegis.xml.MessageWriter;
import org.apache.cxf.common.xmlschema.XmlSchemaUtils;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaAttribute;
import org.apache.ws.commons.schema.XmlSchemaComplexType;
import org.apache.ws.commons.schema.XmlSchemaElement;
import org.apache.ws.commons.schema.XmlSchemaSequence;
import org.qi4j.api.Qi4j;
import org.qi4j.api.association.Association;
import org.qi4j.api.association.AssociationDescriptor;
import org.qi4j.api.association.AssociationStateHolder;
import org.qi4j.api.association.ManyAssociation;
import org.qi4j.api.association.NamedAssociation;
import org.qi4j.api.common.Optional;
import org.qi4j.api.common.QualifiedName;
import org.qi4j.api.composite.StateDescriptor;
import org.qi4j.api.entity.EntityReference;
import org.qi4j.api.entity.Identity;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.injection.scope.Uses;
import org.qi4j.api.property.Property;
import org.qi4j.api.property.PropertyDescriptor;
import org.qi4j.api.structure.Module;
import org.qi4j.api.util.Classes;
import org.qi4j.api.value.NoSuchValueException;
import org.qi4j.api.value.ValueBuilder;
import org.qi4j.api.value.ValueComposite;
import org.qi4j.api.value.ValueDescriptor;
import org.qi4j.functional.Function;
import org.qi4j.functional.Iterables;
import org.qi4j.spi.Qi4jSPI;
import static org.qi4j.functional.Iterables.first;
public class ValueCompositeCxfType
extends AegisType
{
@Structure
private Module module;
@Structure
Qi4jSPI spi;
public ValueCompositeCxfType( @Uses Type type, @Uses TypeMapping typeMapping )
{
setTypeMapping( typeMapping );
setTypeClass( type );
setSchemaType( NamespaceUtil.convertJavaTypeToQName( type ) );
}
@Override
public Object readObject( final MessageReader reader, final Context context )
throws DatabindingException
{
QName qname = getSchemaType();
final String className = ( qname.getNamespaceURI() + "." + qname.getLocalPart() ).substring( 20 );
// Read attributes
ValueDescriptor descriptor = module.valueDescriptor( className );
StateDescriptor stateDescriptor = descriptor.state();
final Map<QualifiedName, Object> values = new HashMap<>();
while( reader.hasMoreElementReaders() )
{
MessageReader childReader = reader.getNextElementReader();
QName childName = childReader.getName();
QualifiedName childQualifiedName = QualifiedName.fromClass( (Class) typeClass,
childName.getLocalPart() );
PropertyDescriptor propertyDescriptor = stateDescriptor.findPropertyModelByQualifiedName(
childQualifiedName );
Type propertyType = propertyDescriptor.type();
AegisType type = getTypeMapping().getType( propertyType );
Object value = type.readObject( childReader, context );
values.put( childQualifiedName, value );
}
ValueBuilder<?> builder = module.newValueBuilderWithState(
(Class<?>) typeClass,
new Function<PropertyDescriptor, Object>()
{
@Override
public Object map( PropertyDescriptor descriptor1 )
{
return values.get( descriptor1.qualifiedName() );
}
},
new Function<AssociationDescriptor, EntityReference>()
{
@Override
public EntityReference map( AssociationDescriptor descriptor )
{
Object value = values.get( descriptor.qualifiedName() );
if( value == null )
{
return null;
}
return EntityReference.parseEntityReference( value.toString() );
}
},
new Function<AssociationDescriptor, Iterable<EntityReference>>()
{
@Override
public Iterable<EntityReference> map( AssociationDescriptor descriptor )
{
Object value = values.get( descriptor.qualifiedName() );
if( value == null )
{
return Iterables.empty();
}
String[] ids = value.toString().split( "," );
List<EntityReference> references = new ArrayList<>( ids.length );
for( String id : ids )
{
references.add( EntityReference.parseEntityReference( id ) );
}
return references;
}
},
new Function<AssociationDescriptor, Map<String, EntityReference>>()
{
@Override
public Map<String, EntityReference> map( AssociationDescriptor descriptor )
{
Object value = values.get( descriptor.qualifiedName() );
if( value == null )
{
return Collections.emptyMap();
}
String[] namedRefs = value.toString().split( "," );
Map<String, EntityReference> references = new HashMap<>( namedRefs.length );
for( String namedRef : namedRefs )
{
String[] splitted = namedRef.split( ":" );
references.put( splitted[0], EntityReference.parseEntityReference( splitted[1] ) );
}
return references;
}
} );
return builder.newInstance();
}
@Override
public void writeObject( Object object, final MessageWriter writer, final Context context )
throws DatabindingException
{
ValueComposite composite = (ValueComposite) object;
writer.writeXsiType( NamespaceUtil.convertJavaTypeToQName(
first( Qi4j.FUNCTION_DESCRIPTOR_FOR.map( composite ).types() ) ) );
AssociationStateHolder state = spi.stateOf( composite );
for( Property<?> property : state.properties() )
{
Object value = property.get();
AegisType type = null;
if( value instanceof ValueComposite )
{
ValueComposite compositeValue = (ValueComposite) value;
type = getTypeMapping().getType( NamespaceUtil.convertJavaTypeToQName(
first( Qi4j.FUNCTION_DESCRIPTOR_FOR.map( compositeValue ).types() ) ) );
}
else if( value != null )
{
type = getOrCreateNonQi4jType( value );
}
QName childName = new QName( "", spi.propertyDescriptorFor( property ).qualifiedName().name() );
MessageWriter cwriter = writer.getElementWriter( childName );
if( type != null )
{
type.writeObject( value, cwriter, context );
}
else
{
// cwriter.writeXsiNil();
}
cwriter.close();
}
AegisType type = getTypeMapping().getType( NamespaceUtil.convertJavaTypeToQName( String.class ) );
for( Association<?> association : state.allAssociations() )
{
QName childName = new QName( "", spi.associationDescriptorFor( association ).qualifiedName().name() );
MessageWriter cwriter = writer.getElementWriter( childName );
if( association.get() != null )
{
type.writeObject( ( (Identity) association.get() ).identity().get(), cwriter, context );
}
cwriter.close();
}
for( ManyAssociation<?> association : state.allManyAssociations() )
{
QName childName = new QName( "", spi.associationDescriptorFor( association ).qualifiedName().name() );
MessageWriter cwriter = writer.getElementWriter( childName );
String ids = null;
for( Object entity : association )
{
String id = EntityReference.entityReferenceFor( entity ).identity();
if( ids != null )
{
ids += ",";
}
ids += id;
}
if( ids == null )
{
ids = "";
}
type.writeObject( ids, cwriter, context );
cwriter.close();
}
for( NamedAssociation<?> association : state.allNamedAssociations() )
{
QName childName = new QName( "", spi.associationDescriptorFor( association ).qualifiedName().name() );
MessageWriter cwriter = writer.getElementWriter( childName );
String ids = null;
for( String name : association )
{
String id = EntityReference.entityReferenceFor( association.get( name ) ).identity();
if( ids != null )
{
ids += ",";
}
ids += name + ":" + id;
}
if( ids == null )
{
ids = "";
}
type.writeObject( ids, cwriter, context );
cwriter.close();
}
}
private AegisType getOrCreateNonQi4jType( Object value )
{
AegisType type;
TypeMapping mapping = getTypeMapping();
Class<?> javaType = value.getClass();
type = mapping.getType( javaType );
if( type == null )
{
// This might be wrong and instead the ultimate top parent should be used. This works, since
// we know that we are the top parent.
type = getTypeMapping().getTypeCreator().createType( javaType );
mapping.register( type );
}
return type;
}
@Override
public void writeSchema( XmlSchema root )
{
XmlSchemaComplexType complex = new XmlSchemaComplexType( root, true );
complex.setName( getSchemaType().getLocalPart() );
root.getItems().add( complex );
XmlSchemaSequence sequence = new XmlSchemaSequence(); // No clue why this?
complex.setParticle( sequence ); // No idea what this is for
ValueDescriptor descriptor = module.valueDescriptor( getTypeClass().getName() );
for( PropertyDescriptor p : descriptor.state().properties() )
{
if( isValueComposite( p.type() ) )
{
XmlSchemaElement element = new XmlSchemaElement( root, false );
element.setName( p.qualifiedName().name() );
element.setNillable( p.metaInfo( Optional.class ) != null ); // see below
sequence.getItems().add( element );
AegisType nested = getOrCreateAegisType( p.type(), root );
element.setSchemaTypeName( nested.getSchemaType() );
}
else if( isCollectionOrMap( p ) )
{
XmlSchemaElement element = new XmlSchemaElement( root, false );
element.setName( p.qualifiedName().name() );
element.setNillable( p.metaInfo( Optional.class ) != null ); // see below
sequence.getItems().add( element );
AegisType nested = getOrCreateAegisType( p.type(), root );
element.setSchemaTypeName( nested.getSchemaType() );
}
else
{
XmlSchemaAttribute attribute = new XmlSchemaAttribute( root, false );
complex.getAttributes().add( attribute );
attribute.setName( p.qualifiedName().name() );
AegisType nested = getTypeMapping().getType( p.type() );
attribute.setSchemaTypeName( nested.getSchemaType() );
}
QName name = NamespaceUtil.convertJavaTypeToQName( p.type() );
String ns = name.getNamespaceURI();
if( !ns.equals( root.getTargetNamespace() ) )
{
XmlSchemaUtils.addImportIfNeeded( root, ns );
}
}
}
private AegisType getOrCreateAegisType( Type type, XmlSchema root )
{
AegisType nested = getTypeMapping().getType( type );
if( nested == null )
{
nested = createType( type, root );
nested.writeSchema( root );
}
return nested;
}
private AegisType createType( Type type, XmlSchema root )
{
if( isCollection( type ) )
{
AegisType componentType = getOrCreateAegisType( getCollectionComponentType( type ), root );
CollectionType resultType = new CollectionType( componentType );
QName schemaType = new QName( "http://www.w3.org/2001/XMLSchema", "list" );
resultType.setSchemaType( schemaType );
return resultType;
}
else if( isMap( type ) )
{
AegisType keyType = getOrCreateAegisType( getMapKeyComponentType( type ), root );
AegisType valueType = getOrCreateAegisType( getMapValueComponentType( type ), root );
QName schemaType = new QName( Classes.toURI( Classes.RAW_CLASS.map( type ) ), "map" );
return new MapType( schemaType, keyType, valueType );
}
else if( isValueComposite( type ) )
{
ValueCompositeCxfType aegisType = module.newObject( ValueCompositeCxfType.class, getTypeMapping(), type );
getTypeMapping().register( aegisType );
return aegisType;
}
else
{
throw new NoSuchValueException( type.toString(), module.name() );
}
}
private boolean isCollectionOrMap( final PropertyDescriptor p )
{
Type type = p.type();
return isCollectionOrMap( type );
}
private boolean isCollection( Type type )
{
if( isCollectionClass( type ) )
{
return true;
}
if( type instanceof ParameterizedType )
{
ParameterizedType param = (ParameterizedType) type;
Type rawType = param.getRawType();
if( isCollectionClass( rawType ) )
{
return true;
}
}
return false;
}
private boolean isMap( Type type )
{
if( isMapClass( type ) )
{
return true;
}
if( type instanceof ParameterizedType )
{
ParameterizedType param = (ParameterizedType) type;
Type rawType = param.getRawType();
if( isCollectionClass( rawType ) )
{
return true;
}
}
return false;
}
private boolean isCollectionOrMap( Type type )
{
return isMap( type ) || isCollection( type );
}
@SuppressWarnings( "raw" )
private boolean isCollectionClass( Type type )
{
if( type instanceof Class )
{
Class clazz = (Class) type;
return Collection.class.isAssignableFrom( clazz );
}
return false;
}
@SuppressWarnings( "raw" )
private boolean isMapClass( Type type )
{
if( type instanceof Class )
{
Class clazz = (Class) type;
return Map.class.isAssignableFrom( clazz );
}
return false;
}
private Type getCollectionComponentType( Type p )
{
return getActualTypeArgument( p, 0 );
}
private Type getMapKeyComponentType( Type p )
{
return getActualTypeArgument( p, 0 );
}
private Type getMapValueComponentType( Type p )
{
return getActualTypeArgument( p, 1 );
}
private Type getActualTypeArgument( Type p, int index )
{
if( p instanceof ParameterizedType )
{
ParameterizedType type = (ParameterizedType) p;
return type.getActualTypeArguments()[index];
}
return null;
}
@SuppressWarnings( "raw" )
private boolean isValueComposite( Type type )
{
Class clazz = Classes.RAW_CLASS.map( type );
ValueDescriptor descriptor = module.valueDescriptor( clazz.getName() );
return descriptor != null;
}
@Override
public boolean isComplex()
{
return true;
}
}