/*
* Copyright 2010 Niclas Hedhman.
*
* 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.*;
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.*;
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.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.*;
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<QualifiedName, Object>();
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;
else
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();
else
{
String[] ids = value.toString().split( "," );
List<EntityReference> references = new ArrayList<EntityReference>( );
for( int i = 0; i < ids.length; i++ )
{
String id = ids[i];
references.add( EntityReference.parseEntityReference( id ) );
}
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+=ids;
}
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 );
}
private boolean isCollectionClass( Type type )
{
if( type instanceof Class )
{
Class clazz = (Class) type;
return Collection.class.isAssignableFrom( clazz );
}
return false;
}
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;
}
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;
}
}