/******************************************************************************* | |
* Copyright (c) 2011, 2013 Oracle. All rights reserved. | |
* This program and the accompanying materials are made available under the | |
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* Contributors: | |
* tware - Initial implementation | |
******************************************************************************/ | |
package org.eclipse.persistence.jpa.rs.util; | |
import java.lang.reflect.Modifier; | |
import java.util.ArrayList; | |
import java.util.Map; | |
import java.util.Vector; | |
import org.eclipse.persistence.descriptors.ClassDescriptor; | |
import org.eclipse.persistence.descriptors.InheritancePolicy; | |
import org.eclipse.persistence.internal.descriptors.InstantiationPolicy; | |
import org.eclipse.persistence.internal.descriptors.VirtualAttributeAccessor; | |
import org.eclipse.persistence.internal.dynamic.ValuesAccessor; | |
import org.eclipse.persistence.internal.jaxb.SessionEventListener; | |
import org.eclipse.persistence.internal.jaxb.XMLJavaTypeConverter; | |
import org.eclipse.persistence.internal.jpa.rs.metadata.model.Link; | |
import org.eclipse.persistence.internal.jpa.weaving.RestAdapterClassWriter; | |
import org.eclipse.persistence.internal.queries.CollectionContainerPolicy; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.internal.weaving.PersistenceWeavedRest; | |
import org.eclipse.persistence.jaxb.DefaultXMLNameTransformer; | |
import org.eclipse.persistence.jpa.rs.exceptions.JPARSException; | |
import org.eclipse.persistence.jpa.rs.util.xmladapters.RelationshipLinkAdapter; | |
import org.eclipse.persistence.mappings.DatabaseMapping; | |
import org.eclipse.persistence.mappings.ForeignReferenceMapping; | |
import org.eclipse.persistence.oxm.XMLDescriptor; | |
import org.eclipse.persistence.oxm.XMLField; | |
import org.eclipse.persistence.oxm.mappings.XMLChoiceCollectionMapping; | |
import org.eclipse.persistence.oxm.mappings.XMLChoiceObjectMapping; | |
import org.eclipse.persistence.oxm.mappings.XMLCompositeCollectionMapping; | |
import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping; | |
import org.eclipse.persistence.oxm.mappings.XMLInverseReferenceMapping; | |
import org.eclipse.persistence.sessions.Project; | |
import org.eclipse.persistence.sessions.SessionEvent; | |
/** | |
* This adapter alters the way the JAXBContext handles relationships for an existing persistence unit. | |
* It changes non-private relationship mappings to be read-only and replaces those mappings with a mapping | |
* to a weaved-in list of relationships that will produce links. | |
* @author tware | |
* | |
*/ | |
public class PreLoginMappingAdapter extends SessionEventListener { | |
protected AbstractSession jpaSession; | |
/** | |
* Instantiates a new pre login mapping adapter. | |
* | |
* @param jpaSession the jpa session | |
*/ | |
public PreLoginMappingAdapter(AbstractSession jpaSession) { | |
this.jpaSession = jpaSession; | |
} | |
/* (non-Javadoc) | |
* @see org.eclipse.persistence.internal.jaxb.SessionEventListener#preLogin(org.eclipse.persistence.sessions.SessionEvent) | |
*/ | |
@SuppressWarnings({ "unchecked", "rawtypes" }) | |
public void preLogin(SessionEvent event) { | |
Project project = event.getSession().getProject(); | |
ClassLoader cl = jpaSession.getDatasourcePlatform().getConversionManager().getLoader(); | |
DefaultXMLNameTransformer xmlNameTransformer = new DefaultXMLNameTransformer(); | |
for (Object descriptorAlias : project.getAliasDescriptors().keySet()) { | |
ClassDescriptor descriptor = (ClassDescriptor) project.getAliasDescriptors().get(descriptorAlias); | |
if (!PersistenceWeavedRest.class.isAssignableFrom(descriptor.getJavaClass())) { | |
continue; | |
} | |
if (descriptor.isXMLDescriptor()) { | |
XMLDescriptor xmlDescriptor = (XMLDescriptor) project.getAliasDescriptors().get(descriptorAlias); | |
if (null != xmlDescriptor) { | |
if (null == xmlDescriptor.getDefaultRootElement()) { | |
// set root element | |
xmlDescriptor.setDefaultRootElement(xmlNameTransformer.transformRootElementName(xmlDescriptor.getJavaClass().getName())); | |
// set resultAlwaysXMLRoot to false, so that the elements are not wrapped in JAXBElements when unmarshalling. | |
xmlDescriptor.setResultAlwaysXMLRoot(false); | |
} | |
} | |
} | |
XMLCompositeCollectionMapping relationshipMapping = new XMLCompositeCollectionMapping(); | |
relationshipMapping.setAttributeName("_persistence_relationshipInfo"); | |
relationshipMapping.setGetMethodName("_persistence_getRelationships"); | |
relationshipMapping.setSetMethodName("_persistence_setRelationships"); | |
relationshipMapping.setDescriptor(descriptor); | |
CollectionContainerPolicy containerPolicy = new CollectionContainerPolicy(ArrayList.class); | |
relationshipMapping.setContainerPolicy(containerPolicy); | |
relationshipMapping.setField(new XMLField("_relationships")); | |
relationshipMapping.setReferenceClass(Link.class); | |
XMLJavaTypeConverter converter = new XMLJavaTypeConverter(RelationshipLinkAdapter.class); | |
converter.initialize(relationshipMapping, event.getSession()); | |
relationshipMapping.setConverter(converter); | |
descriptor.addMapping(relationshipMapping); | |
XMLCompositeObjectMapping hrefMapping = new XMLCompositeObjectMapping(); | |
hrefMapping.setAttributeName("_persistence_href"); | |
hrefMapping.setGetMethodName("_persistence_getHref"); | |
hrefMapping.setSetMethodName("_persistence_setHref"); | |
hrefMapping.setDescriptor(descriptor); | |
hrefMapping.setField(new XMLField("_link")); | |
hrefMapping.setReferenceClass(Link.class); | |
hrefMapping.setXPath("."); | |
descriptor.addMapping(hrefMapping); | |
ClassDescriptor jpaDescriptor = jpaSession.getDescriptorForAlias(descriptor.getAlias()); | |
Vector<DatabaseMapping> descriptorMappings = (Vector<DatabaseMapping>) descriptor.getMappings().clone(); | |
for (DatabaseMapping mapping : descriptorMappings) { | |
if (mapping.isXMLMapping()) { | |
if (mapping.isAbstractCompositeObjectMapping() || mapping.isAbstractCompositeCollectionMapping()) { | |
if (mapping.isAbstractCompositeCollectionMapping()) { | |
XMLInverseReferenceMapping inverseMapping = ((XMLCompositeCollectionMapping) mapping).getInverseReferenceMapping(); | |
if (inverseMapping != null) { | |
break; | |
} | |
} else if (mapping.isAbstractCompositeObjectMapping()) { | |
XMLInverseReferenceMapping inverseMapping = ((XMLCompositeObjectMapping) mapping).getInverseReferenceMapping(); | |
if (inverseMapping != null) { | |
break; | |
} | |
} | |
if (jpaDescriptor != null) { | |
DatabaseMapping dbMapping = jpaDescriptor.getMappingForAttributeName(mapping.getAttributeName()); | |
if ((dbMapping != null) && (dbMapping instanceof ForeignReferenceMapping)) { | |
ForeignReferenceMapping jpaMapping = (ForeignReferenceMapping) dbMapping; | |
if (jpaMapping != null) { | |
if (jpaMapping.getMappedBy() != null) { | |
ClassDescriptor inverseDescriptor = project.getDescriptorForAlias(jpaMapping.getReferenceDescriptor().getAlias()); | |
if (inverseDescriptor != null) { | |
DatabaseMapping inverseMapping = inverseDescriptor.getMappingForAttributeName(jpaMapping.getMappedBy()); | |
if (inverseMapping != null) { | |
convertMappingToXMLInverseReferenceMapping(inverseDescriptor, inverseMapping, jpaMapping); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
InheritancePolicy inheritancePolicy = descriptor.getInheritancePolicyOrNull(); | |
if ((inheritancePolicy != null) && (inheritancePolicy.isRootParentDescriptor())) { | |
boolean isAbstract = Modifier.isAbstract(descriptor.getJavaClass().getModifiers()); | |
if (isAbstract) { | |
Class subClassToInstantiate = null; | |
Map<?, ?> classIndicatorMapping = inheritancePolicy.getClassIndicatorMapping(); | |
// get one of subclasses that extends this abstract class | |
for (Map.Entry<?, ?> entry : classIndicatorMapping.entrySet()) { | |
Object value = entry.getValue(); | |
if (value instanceof Class) { | |
subClassToInstantiate = (Class) value; | |
isAbstract = Modifier.isAbstract(subClassToInstantiate.getModifiers()); | |
if ((subClassToInstantiate != null) && (!isAbstract)) { | |
InstantiationPolicy instantiationPolicy = new InstantiationPolicy(); | |
instantiationPolicy.useFactoryInstantiationPolicy(new ConcreteSubclassFactory(subClassToInstantiate), "createConcreteSubclass"); | |
descriptor.setInstantiationPolicy(instantiationPolicy); | |
break; | |
} | |
} | |
} | |
} | |
} | |
} | |
for (Object descriptorAlias : project.getAliasDescriptors().keySet()) { | |
ClassDescriptor descriptor = (ClassDescriptor) project.getAliasDescriptors().get(descriptorAlias); | |
ClassDescriptor jpaDescriptor = jpaSession.getDescriptorForAlias(descriptor.getAlias()); | |
Vector<DatabaseMapping> descriptorMappings = (Vector<DatabaseMapping>) descriptor.getMappings().clone(); | |
for (DatabaseMapping mapping : descriptorMappings) { | |
if (mapping.isXMLMapping()) { | |
if (mapping.isAbstractCompositeObjectMapping() || mapping.isAbstractCompositeCollectionMapping()) { | |
if (jpaDescriptor != null) { | |
DatabaseMapping dbMapping = jpaDescriptor.getMappingForAttributeName(mapping.getAttributeName()); | |
if ((dbMapping instanceof ForeignReferenceMapping)) { | |
ForeignReferenceMapping jpaMapping = (ForeignReferenceMapping) dbMapping; | |
if (jpaMapping != null) { | |
ClassDescriptor jaxbDescriptor = project.getDescriptorForAlias(jpaMapping.getDescriptor().getAlias()); | |
if (jaxbDescriptor != null) { | |
convertMappingToXMLChoiceMapping(jaxbDescriptor, jpaMapping, cl, jpaSession); | |
} | |
} | |
} | |
} else if (mapping instanceof XMLCompositeObjectMapping) { | |
// Fix for Bug 403113 - JPA-RS Isn't Serializing an Embeddable defined in an ElementCollection to JSON Correctly | |
// add choice mapping for one-to-one relationships within embeddables | |
// Based on (http://wiki.eclipse.org/EclipseLink/Examples/JPA/NoSQL#Step_2_:_Map_the_data), | |
// the mappedBy option on relationships is not supported for NoSQL data, so no need to add inverse mapping | |
XMLCompositeObjectMapping jpaMapping = (XMLCompositeObjectMapping) mapping; | |
if (jpaMapping != null) { | |
ClassDescriptor jaxbDescriptor = project.getDescriptorForAlias(jpaMapping.getDescriptor().getAlias()); | |
if (jaxbDescriptor != null) { | |
Class clazz = jpaMapping.getReferenceClass(); | |
if (clazz != null) { | |
if ((jpaSession.getDescriptor(clazz) != null) && (jpaSession.getDescriptor(clazz).isEISDescriptor())) | |
convertMappingToXMLChoiceMapping(jaxbDescriptor, jpaMapping, cl, jpaSession); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Update the targetMapping to have the same accessor as the originMapping | |
* @param originMapping | |
* @param targetMapping | |
*/ | |
private static void copyAccessorToMapping(DatabaseMapping originMapping, DatabaseMapping targetMapping) { | |
if (originMapping.getAttributeAccessor().isVirtualAttributeAccessor()) { | |
VirtualAttributeAccessor accessor = new VirtualAttributeAccessor(); | |
accessor.setGetMethodName(originMapping.getGetMethodName()); | |
accessor.setSetMethodName(originMapping.getSetMethodName()); | |
targetMapping.setAttributeAccessor(accessor); | |
} | |
if (originMapping.getAttributeAccessor().isValuesAccessor()) { | |
ValuesAccessor accessor = new ValuesAccessor(originMapping); | |
accessor.setAttributeName(originMapping.getAttributeAccessor().getAttributeName()); | |
targetMapping.setAttributeAccessor(accessor); | |
} else { | |
targetMapping.setAttributeName(originMapping.getAttributeName()); | |
targetMapping.setGetMethodName(originMapping.getGetMethodName()); | |
targetMapping.setSetMethodName(originMapping.getSetMethodName()); | |
} | |
} | |
/** | |
* Build an XMLInverseMapping based on a particular mapping and replace that mapping with | |
* the newly created XMLInverseMapping in jaxbDescriptor | |
* @param jaxbDescriptor | |
* @param mapping | |
* @param mappedBy | |
*/ | |
private static void convertMappingToXMLInverseReferenceMapping(ClassDescriptor jaxbDescriptor, DatabaseMapping mapping, ForeignReferenceMapping jpaMapping) { | |
if ((mapping != null) && (jaxbDescriptor != null)) { | |
if (!(mapping.isXMLMapping())) { | |
return; | |
} | |
if ((jpaMapping.isAggregateCollectionMapping()) || (jpaMapping.isAggregateMapping())) { | |
return; | |
} | |
XMLInverseReferenceMapping jaxbInverseMapping = new XMLInverseReferenceMapping(); | |
copyAccessorToMapping(mapping, jaxbInverseMapping); | |
jaxbInverseMapping.setProperties(mapping.getProperties()); | |
jaxbInverseMapping.setIsReadOnly(mapping.isReadOnly()); | |
jaxbInverseMapping.setMappedBy(jpaMapping.getAttributeName()); | |
if (mapping.isAbstractCompositeCollectionMapping()) { | |
jaxbInverseMapping.setContainerPolicy(mapping.getContainerPolicy()); | |
jaxbInverseMapping.setReferenceClass(((XMLCompositeCollectionMapping) mapping).getReferenceClass()); | |
} else if (mapping.isAbstractCompositeObjectMapping()) { | |
jaxbInverseMapping.setReferenceClass(((XMLCompositeObjectMapping) mapping).getReferenceClass()); | |
} | |
jaxbDescriptor.removeMappingForAttributeName(mapping.getAttributeName()); | |
jaxbDescriptor.addMapping(jaxbInverseMapping); | |
} | |
} | |
/** | |
* Build an XMLChoiceObjectMapping based on a particular mapping and replace that mapping with | |
* the newly created XMLChoiceObjectMapping in jaxbDescriptor. | |
* @param jaxbDescriptor the jaxb descriptor | |
* @param jpaMapping the jpa mapping | |
* @param cl the classloader | |
*/ | |
@SuppressWarnings("rawtypes") | |
private static void convertMappingToXMLChoiceMapping(ClassDescriptor jaxbDescriptor, DatabaseMapping jpaMapping, ClassLoader cl, AbstractSession jpaSession) { | |
if ((jpaMapping != null) && (jaxbDescriptor != null)) { | |
if ((jpaMapping instanceof ForeignReferenceMapping) && ((jpaMapping.isAggregateCollectionMapping()) || (jpaMapping.isAggregateMapping()))) { | |
// Fix for Bug 402385 - JPA-RS: ClassNotFound when using ElementCollection of Embeddables | |
// Aggregates don't have identity to create links, thus no weaved REST adapters to insert choice mappings | |
return; | |
} | |
String attributeName = jpaMapping.getAttributeName(); | |
DatabaseMapping jaxbMapping = jaxbDescriptor.getMappingForAttributeName(jpaMapping.getAttributeName()); | |
if (!(jaxbMapping.isXMLMapping() && (jaxbMapping.isAbstractCompositeCollectionMapping() || jaxbMapping.isAbstractCompositeObjectMapping()))) { | |
return; | |
} | |
ClassDescriptor refDesc = null; | |
if (jpaMapping instanceof ForeignReferenceMapping) { | |
Class clazz = ((ForeignReferenceMapping) jpaMapping).getReferenceClass(); | |
refDesc = jpaSession.getDescriptor(clazz); | |
} else if (jpaMapping instanceof XMLCompositeObjectMapping) { | |
Class clazz = ((XMLCompositeObjectMapping) jpaMapping).getReferenceClass(); | |
refDesc = jpaSession.getDescriptor(clazz); | |
} | |
if (refDesc == null) { | |
return; | |
} | |
String adapterClassName = RestAdapterClassWriter.constructClassNameForReferenceAdapter(refDesc.getJavaClassName()); | |
if (adapterClassName != null) { | |
try { | |
if (jaxbMapping.isAbstractCompositeObjectMapping()) { | |
XMLChoiceObjectMapping xmlChoiceMapping = new XMLChoiceObjectMapping(); | |
xmlChoiceMapping.setAttributeName(attributeName); | |
copyAccessorToMapping(jaxbMapping, xmlChoiceMapping); | |
xmlChoiceMapping.setProperties(jaxbMapping.getProperties()); | |
XMLCompositeObjectMapping compositeMapping = (XMLCompositeObjectMapping) jaxbMapping; | |
xmlChoiceMapping.addChoiceElement(compositeMapping.getXPath(), Link.class); | |
xmlChoiceMapping.addChoiceElement(compositeMapping.getXPath(), refDesc.getJavaClass()); | |
xmlChoiceMapping.setConverter(new XMLJavaTypeConverter(Class.forName(adapterClassName, true, cl))); | |
jaxbDescriptor.removeMappingForAttributeName(jaxbMapping.getAttributeName()); | |
jaxbDescriptor.addMapping(xmlChoiceMapping); | |
} else if (jaxbMapping.isAbstractCompositeCollectionMapping()) { | |
XMLChoiceCollectionMapping xmlChoiceMapping = new XMLChoiceCollectionMapping(); | |
xmlChoiceMapping.setAttributeName(attributeName); | |
copyAccessorToMapping(jaxbMapping, xmlChoiceMapping); | |
xmlChoiceMapping.setProperties(jaxbMapping.getProperties()); | |
XMLCompositeCollectionMapping compositeMapping = (XMLCompositeCollectionMapping) jaxbMapping; | |
xmlChoiceMapping.addChoiceElement(compositeMapping.getXPath(), Link.class); | |
xmlChoiceMapping.addChoiceElement(compositeMapping.getXPath(), refDesc.getJavaClass()); | |
xmlChoiceMapping.setContainerPolicy(jaxbMapping.getContainerPolicy()); | |
xmlChoiceMapping.setConverter(new XMLJavaTypeConverter(Class.forName(adapterClassName, true, cl))); | |
jaxbDescriptor.removeMappingForAttributeName(jaxbMapping.getAttributeName()); | |
jaxbDescriptor.addMapping(xmlChoiceMapping); | |
} | |
} catch (ClassNotFoundException e) { | |
throw new JPARSException(e.toString()); | |
} | |
} | |
} | |
} | |
} |