Merge pull request #81 from tandraschko/master

OPENJPA-2877 Implement JPA2.1 @Convert / AttributeConverter
This commit is contained in:
Romain Manni-Bucau 2021-10-27 12:14:20 +02:00 committed by GitHub
commit cb258aee42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 195 additions and 67 deletions

View File

@ -188,6 +188,7 @@ public class FieldMetaData
private Boolean _serializableField = null; private Boolean _serializableField = null;
private boolean _generated = false; private boolean _generated = false;
private boolean _useSchemaElement = true; private boolean _useSchemaElement = true;
private Class _converter;
// Members aren't serializable. Use a proxy that can provide a Member // Members aren't serializable. Use a proxy that can provide a Member
// to avoid writing the full Externalizable implementation. // to avoid writing the full Externalizable implementation.
@ -198,6 +199,10 @@ public class FieldMetaData
private transient Method _extMethod = DEFAULT_METHOD; private transient Method _extMethod = DEFAULT_METHOD;
private transient Member _factMethod = DEFAULT_METHOD; private transient Member _factMethod = DEFAULT_METHOD;
private transient Constructor _converterConstructor;
private transient Method _converterExtMethod;
private transient Method _converterFactMethod;
// intermediate and impl data // intermediate and impl data
private boolean _intermediate = true; private boolean _intermediate = true;
private Boolean _implData = Boolean.TRUE; private Boolean _implData = Boolean.TRUE;
@ -1317,35 +1322,58 @@ public class FieldMetaData
} }
Method externalizer = getExternalizerMethod(); Method externalizer = getExternalizerMethod();
if (externalizer == null) if (externalizer != null) {
return val; // special case for queries: allow the given value to pass through
// as-is if it is already in externalized form
if (val != null && getType().isInstance(val)
&& (!getDeclaredType().isInstance(val)
|| getDeclaredType() == Object.class))
return val;
// special case for queries: allow the given value to pass through try {
// as-is if it is already in externalized form // either invoke the static toExternal(val[, ctx]) method, or the
if (val != null && getType().isInstance(val) // non-static val.toExternal([ctx]) method
&& (!getDeclaredType().isInstance(val) if (Modifier.isStatic(externalizer.getModifiers())) {
|| getDeclaredType() == Object.class)) if (externalizer.getParameterTypes().length == 1)
return val; return externalizer.invoke(null, new Object[]{ val });
return externalizer.invoke(null, new Object[]{ val, ctx });
try { }
// either invoke the static toExternal(val[, ctx]) method, or the if (val == null)
// non-static val.toExternal([ctx]) method return null;
if (Modifier.isStatic(externalizer.getModifiers())) { if (externalizer.getParameterTypes().length == 0)
if (externalizer.getParameterTypes().length == 1) return externalizer.invoke(val, (Object[]) null);
return externalizer.invoke(null, new Object[]{ val }); return externalizer.invoke(val, new Object[]{ ctx });
return externalizer.invoke(null, new Object[]{ val, ctx }); } catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new MetaDataException(_loc.get("externalizer-err", this,
Exceptions.toString(val), e.toString())).setCause(e);
} }
if (val == null)
return null;
if (externalizer.getParameterTypes().length == 0)
return externalizer.invoke(val, (Object[]) null);
return externalizer.invoke(val, new Object[]{ ctx });
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new MetaDataException(_loc.get("externalizer-err", this,
Exceptions.toString(val), e.toString())).setCause(e);
} }
Class converter = getConverter();
if (converter != null && val != null) {
try {
// TODO support CDI (OPENJPA-2714)
if (_converterConstructor == null) {
_converterConstructor = converter.getDeclaredConstructor();
}
Object instance = _converterConstructor.newInstance();
// see AttributeConverter.java from the JPA specs
if (_converterExtMethod == null) {
_converterExtMethod = converter.getDeclaredMethod("convertToDatabaseColumn", Object.class);
}
return _converterExtMethod.invoke(instance, val);
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new MetaDataException(_loc.get("converter-err", this,
Exceptions.toString(val), e.toString())).setCause(e);
}
}
return val;
} }
/** /**
@ -1359,50 +1387,73 @@ public class FieldMetaData
return fieldValues.get(val); return fieldValues.get(val);
Member factory = getFactoryMethod(); Member factory = getFactoryMethod();
if (factory == null) if (factory != null) {
return val; try {
if (val == null && getNullValue() == NULL_DEFAULT)
return AccessController.doPrivileged(
J2DoPrivHelper.newInstanceAction(getDeclaredType()));
try { // invoke either the constructor for the field type,
if (val == null && getNullValue() == NULL_DEFAULT) // or the static type.toField(val[, ctx]) method
return AccessController.doPrivileged( if (factory instanceof Constructor) {
J2DoPrivHelper.newInstanceAction(getDeclaredType())); if (val == null)
return null;
return ((Constructor) factory).newInstance
(new Object[]{ val });
}
// invoke either the constructor for the field type, Method meth = (Method) factory;
// or the static type.toField(val[, ctx]) method if (meth.getParameterTypes().length == 1)
if (factory instanceof Constructor) { return meth.invoke(null, new Object[]{ val });
if (val == null) return meth.invoke(null, new Object[]{ val, ctx });
return null; } catch (Exception e) {
return ((Constructor) factory).newInstance // unwrap cause
(new Object[]{ val }); if (e instanceof InvocationTargetException) {
Throwable t = ((InvocationTargetException) e).
getTargetException();
if (t instanceof Error)
throw (Error) t;
e = (Exception) t;
// allow null values to cause NPEs and illegal arg exceptions
// without error
if (val == null && (e instanceof NullPointerException
|| e instanceof IllegalArgumentException))
return null;
}
if (e instanceof OpenJPAException)
throw (OpenJPAException) e;
if (e instanceof PrivilegedActionException)
e = ((PrivilegedActionException) e).getException();
throw new MetaDataException(_loc.get("factory-err", this,
Exceptions.toString(val), e.toString())).setCause(e);
} }
Method meth = (Method) factory;
if (meth.getParameterTypes().length == 1)
return meth.invoke(null, new Object[]{ val });
return meth.invoke(null, new Object[]{ val, ctx });
} catch (Exception e) {
// unwrap cause
if (e instanceof InvocationTargetException) {
Throwable t = ((InvocationTargetException) e).
getTargetException();
if (t instanceof Error)
throw (Error) t;
e = (Exception) t;
// allow null values to cause NPEs and illegal arg exceptions
// without error
if (val == null && (e instanceof NullPointerException
|| e instanceof IllegalArgumentException))
return null;
}
if (e instanceof OpenJPAException)
throw (OpenJPAException) e;
if (e instanceof PrivilegedActionException)
e = ((PrivilegedActionException) e).getException();
throw new MetaDataException(_loc.get("factory-err", this,
Exceptions.toString(val), e.toString())).setCause(e);
} }
Class converter = getConverter();
if (converter != null && val != null) {
try {
// TODO support CDI (OPENJPA-2714)
if (_converterConstructor == null) {
_converterConstructor = converter.getDeclaredConstructor();
}
Object instance = _converterConstructor.newInstance();
// see AttributeConverter.java from the JPA specs
if (_converterFactMethod == null) {
_converterFactMethod = converter.getDeclaredMethod("convertToEntityAttribute", Object.class);
}
return _converterFactMethod.invoke(instance, val);
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw new MetaDataException(_loc.get("converter-err", this,
Exceptions.toString(val), e.toString())).setCause(e);
}
}
return val;
} }
/** /**
@ -1420,6 +1471,12 @@ public class FieldMetaData
_extMethod = DEFAULT_METHOD; _extMethod = DEFAULT_METHOD;
} }
public void setConverter(Class converter) {
_converter = converter;
_converterExtMethod = null;
_converterFactMethod = null;
}
/** /**
* The name of this field's factory, or null if none. * The name of this field's factory, or null if none.
*/ */
@ -2044,6 +2101,7 @@ public class FieldMetaData
_access = field._access; _access = field._access;
_orderDec = field._orderDec; _orderDec = field._orderDec;
_useSchemaElement = field._useSchemaElement; _useSchemaElement = field._useSchemaElement;
_converter = field._converter;
// embedded fields can't be versions // embedded fields can't be versions
if (_owner.getEmbeddingMetaData() == null && _version == null) if (_owner.getEmbeddingMetaData() == null && _version == null)
@ -2523,4 +2581,8 @@ public class FieldMetaData
} }
return setterName; return setterName;
} }
public Class getConverter() {
return _converter;
}
} }

View File

@ -119,6 +119,8 @@ bad-externalizer: The externalizer method "{1}" on field "{0}" is not valid. \
"<owning-class>.<method-name>", and that the method is static. "<owning-class>.<method-name>", and that the method is static.
externalizer-err: There was an error invoking the externalizer for field \ externalizer-err: There was an error invoking the externalizer for field \
"{0}" on Java value "{1}": {2} "{0}" on Java value "{1}": {2}
converter-err: There was an error invoking the converter for field \
"{0}" on Java value "{1}": {2}
factory-err: There was an error invoking the factory for field \ factory-err: There was an error invoking the factory for field \
"{0}" on datastore value "{1}": {2} "{0}" on datastore value "{1}": {2}
bad-factory: The factory method supplied for field "{0}" does not exist or \ bad-factory: The factory method supplied for field "{0}" does not exist or \

View File

@ -133,6 +133,7 @@ public class TestExternalValues
assertFalse(result.isEmpty()); assertFalse(result.isEmpty());
for (ExternalValues x:result) { for (ExternalValues x:result) {
assertEquals(uuid, x.getUuid()); assertEquals(uuid, x.getUuid());
assertEquals(uuid, x.getUuid2());
} }
} }
@ -148,6 +149,7 @@ public class TestExternalValues
assertFalse(result.isEmpty()); assertFalse(result.isEmpty());
for (ExternalValues pc:result) { for (ExternalValues pc:result) {
assertEquals(uuid, pc.getUuid()); assertEquals(uuid, pc.getUuid());
assertEquals(uuid, pc.getUuid2());
} }
} }
@ -189,6 +191,7 @@ public class TestExternalValues
em.getTransaction().begin(); em.getTransaction().begin();
ExternalValues pc = new ExternalValues(); ExternalValues pc = new ExternalValues();
pc.setUuid(uuid); pc.setUuid(uuid);
pc.setUuid2(uuid);
em.persist(pc); em.persist(pc);
em.getTransaction().commit(); em.getTransaction().commit();
em.clear(); em.clear();

View File

@ -20,6 +20,7 @@ package org.apache.openjpa.persistence.meta.common.apps;
import java.util.UUID; import java.util.UUID;
import javax.persistence.Convert;
import javax.persistence.Entity; import javax.persistence.Entity;
@ -44,6 +45,9 @@ public class ExternalValues {
@Factory("UUID.fromString") @Factory("UUID.fromString")
private UUID uuid; private UUID uuid;
@Convert(converter = UuidAttributeConverter.class)
private UUID uuid2;
public boolean getBooleanToShort() { public boolean getBooleanToShort() {
return booleanToShort; return booleanToShort;
} }
@ -123,4 +127,13 @@ public class ExternalValues {
public void setUuid(UUID uuid) { public void setUuid(UUID uuid) {
this.uuid = uuid; this.uuid = uuid;
} }
public UUID getUuid2() {
return uuid2;
}
public void setUuid2(UUID uuid2) {
this.uuid2 = uuid2;
}
} }

View File

@ -0,0 +1,40 @@
/*
* 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.openjpa.persistence.meta.common.apps;
import java.util.UUID;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter
public class UuidAttributeConverter implements AttributeConverter<UUID, String>
{
@Override
public String convertToDatabaseColumn(UUID attribute)
{
return attribute.toString();
}
@Override
public UUID convertToEntityAttribute(String dbData)
{
return UUID.fromString(dbData);
}
}

View File

@ -96,6 +96,7 @@ import javax.persistence.AccessType;
import javax.persistence.Basic; import javax.persistence.Basic;
import javax.persistence.Cacheable; import javax.persistence.Cacheable;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Convert;
import javax.persistence.ElementCollection; import javax.persistence.ElementCollection;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
import javax.persistence.Embedded; import javax.persistence.Embedded;
@ -174,6 +175,7 @@ import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException; import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.UnsupportedException; import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException; import org.apache.openjpa.util.UserException;
import static org.apache.openjpa.persistence.MetaDataTag.CONVERT;
/** /**
@ -231,6 +233,7 @@ public class AnnotationPersistenceMetaDataParser
_tags.put(ElementType.class, ELEM_TYPE); _tags.put(ElementType.class, ELEM_TYPE);
_tags.put(ExternalValues.class, EXTERNAL_VALS); _tags.put(ExternalValues.class, EXTERNAL_VALS);
_tags.put(Externalizer.class, EXTERNALIZER); _tags.put(Externalizer.class, EXTERNALIZER);
_tags.put(Convert.class, CONVERT);
_tags.put(Factory.class, FACTORY); _tags.put(Factory.class, FACTORY);
_tags.put(FetchGroup.class, FETCH_GROUP); _tags.put(FetchGroup.class, FETCH_GROUP);
_tags.put(FetchGroups.class, FETCH_GROUPS); _tags.put(FetchGroups.class, FETCH_GROUPS);
@ -1326,6 +1329,10 @@ public class AnnotationPersistenceMetaDataParser
fmd.setTypeOverride(toOverrideType(((Type) anno). fmd.setTypeOverride(toOverrideType(((Type) anno).
value())); value()));
break; break;
case CONVERT:
if (isMetaDataMode() && !((Convert) anno).disableConversion())
fmd.setConverter(((Convert) anno).converter());
break;
default: default:
throw new UnsupportedException(_loc.get("unsupported", fmd, throw new UnsupportedException(_loc.get("unsupported", fmd,
anno.toString())); anno.toString()));

View File

@ -91,5 +91,6 @@ public enum MetaDataTag {
OPENJPA_VERSION, OPENJPA_VERSION,
// JPA 2.1 // JPA 2.1
STOREDPROCEDURE_QUERIES, STOREDPROCEDURE_QUERIES,
STOREDPROCEDURE_QUERY STOREDPROCEDURE_QUERY,
CONVERT
} }