Good morning.
I have a problem with the answers of rest services that use generics - I have a generic object that has the following structure:
import java.io.Serializable;
public class Data implements Serializable {
private Object data;
private Long numero_reg;
/**
* #return the data
*/
public Object getData() {
return data;
}
/**
* #param data the data to set
*/
public void setData(Object data) {
this.data = data;
}
/**
* #return the numero_reg
*/
public long getNumero_reg() {
return numero_reg;
}
/**
* #param numero_reg the numero_reg to set
*/
public void setNumero_reg(long numero_reg) {
this.numero_reg = numero_reg;
}
}
When answer a rest one to object to the objects that meet the different rest with the purpose of standardized response follows:
#Path("/pruebas")
public class Pruebita {
#GET
public Data getalgo(){
ObjectoDatos od=new ObjectoDatos();
od.setPrueba("hola");
od.setPrueba2("hola2");
Data sd=new Data();
sd.setData(od);
sd.setNumero_reg(1);
return sd;
}
}
When it meets the response json answer it becomes a toString of the object and is not showing the data of the object.
Can someone help me?
{"data":"bo.ObjectoDatos#6d858b63","numero_reg":1}
This is the error - I got to the toString of the object , and not the object itself.
I found a solution -
I use the HashMaps class from guava repackaged :
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
int response = responseContext.getStatus();
Object entidad = responseContext.getEntity();
if (entidad != null && response == 200) {
int nroRegistros = (entidad instanceof List) ? (((List) entidad).size()) : 1;
Map<String, Object> respuesta = Maps.newHashMap();
respuesta.put("data", entidad);
if(nroRegistros != 1) {
respuesta.put("numero_reg", nroRegistros);
}
responseContext.setEntity(respuesta);
}
}
The above code works well and solves my issues.
Related
I have an object in use throughout my codebase, UnsecureObject. This object is auto-generated with no getters/setters, and all member fields are public. So editing is done by doing something like the following:
unsecureObjInstance.firstName = "Jane";
This is not desirable for numerous reasons that I probably don't have to explain here. But using this generated class is required for some other technical details with our messaging pipeline that I won't go into.
I have a desire is to leverage a mapping utility written by someone else on my team to convert this UnsecureObject to a pojo that I am writing.
An example of the mapper in action (with two normal classes w/ getters/setters) would be something like:
new MapperBuilder<>(PojoOne.class, PojoTwo.class)
.from(PojoOne::getName).to(PojoTwo::getFirstName)
.build();
This will map the PojoOne#name field to the PojoTwo#firstName field.
Is there a way to translate this to input my UnsecureObject here? I have tried something like the following:
new MapperBuilder<>(UnsecureObject.class, SecureObject.class)
.from(u -> u.firstName).to(SecureObject::getFirstName)
.build();
But get an error here, something along the lines of 'u -> u.firstName' could not be invoked.
So the question is:
Is there a way to essentially "construct" a getter on the fly using these public members? So in the .from() method, I can construct the call to look like a standard method that will yield my u.firstName?
Thanks for the help!
EDIT:
this is approx what the MapperBuilder class looks like (attempted to edit a bit to take away project specific wrappers/simplify)
/**
* This class is used to convert between POJO getter method references to the corresponding field names.
* #param <B> type
*/
public interface PojoProxy<B> {
/**
* Invokes the given getter method and returns information about the invocation.
* #param getter the getter to invoke
* #return information about the method invoked
*/
<T> GetterInvocation<T> invokeGetter(Function<B, T> getter);
}
/**
* Stores information about a method invocation.
* #param <T> method return type
*/
public interface GetterInvocation<T> {
public Class<T> getReturnType();
public String getFieldName();
}
/**
* A builder class to create {#link Mapper} instances.
* #param <FROM> source type
* #param <TO> target type
*/
public class MapperBuilder<FROM, TO> {
private final Class<FROM> _fromClass;
private final Class<TO> _toClass;
private final PojoProxy<FROM> _fromProxy;
private final PojoProxy<TO> _toProxy;
public MapperBuilder(Class<FROM> fromClass, Class<TO> toClass) {
_fromClass = fromClass;
_toClass = toClass;
//We will pretend there is an impl that provides the proxy.
//Proxies wrap the from and to classes in order to get reflection information about their getter calls.
_fromProxy = PojoProxy.of(fromClass);
_toProxy = PojoProxy.of(toClass);
}
public <FROM_VALUE> ToFieldBuilder<FROM_VALUE> from(Function<FROM, FROM_VALUE> getter) {
GetterInvocation<FROM_VALUE> methodInvocation = _fromProxy.invokeGetter(getter);
return new ToFieldBuilder<>(methodInvocation.getFieldName(), methodInvocation.getReturnType());
}
public class ToFieldBuilder<FROM_VALUE> {
private final String _fromFieldPath;
private final Class<FROM_VALUE> _fromClass;
public ToFieldBuilder(String fromFieldPath, Class<FROM_VALUE> fromClass) {
_fromFieldPath = fromFieldPath;
_fromClass = fromClass;
}
public <TO_VALUE> FromFieldBuilder<FROM_VALUE, TO_VALUE> to(Function<TO, TO_VALUE> getter) {
//similar to above, but now using a FromFieldBuilder.
}
}
public class FromFieldBuilder<FROM_VALUE, TO_VALUE> {
//impl..
}
}
I dont see MapperBuilder.from() method details, you can try this implementation of MapperBuilder.java Function (getter) -> (BiConsumer) setter
public class MapperBuilder<S, D> {
private final S src;
private final D dest;
public MapperBuilder(S src, Class<D> dest) {
this.src = src;
try {
this.dest = dest.newInstance();
} catch (Exception e) {
throw new RuntimeException("Required default constructor for: " + dest);
}
}
//getter - function to get value from source instance
//setter - biConsumer to set value to destination instance
//example - map(SrcClass::getSrcValue, DestClass::setDestValue)
public <V> MapperBuilder<S, D> map(Function<S, V> getter, BiConsumer<D, V> setter) {
setter.accept(dest, getter.apply(src));
return this;
}
public D build() {
return dest;
}
}
SrcClass.java some source class:
public class SrcClass {
private String srcValue;
public String getSrcValue() {
return srcValue;
}
public void setSrcValue(String srcValue) {
this.srcValue = srcValue;
}
}
DestClass.java some destination class:
package com.example.demo;
public class DestClass {
private String destValue;
public String getDestValue() {
return destValue;
}
public void setDestValue(String destValue) {
this.destValue = destValue;
}
}
DemoApplication.java demo:
public class DemoApplication {
public static void main(String[] args) {
SrcClass src = new SrcClass();
src.setSrcValue("someValue");
DestClass dest = new MapperBuilder<>(src, DestClass.class)
.map(SrcClass::getSrcValue, DestClass::setDestValue)
// map another fields
.build();
// for your UnsecureObject case
UnsecureObject unsecureObject = new MapperBuilder<>(src, UnsecureObject.class)
.map(SrcClass::getSrcValue,
(unsecure, srcValue) -> unsecure.unsecureValue = srcValue)
.build();
}
}
For a project, which generates XML-files based on a XSD-file, I want to automatically generate the documentation. *
In this documentation I list the different elements defined in the XSD.
And for each element I want to show an example of that element.
The problem is, that the XML-example might be quite long and contains a lot of children.
Therefore I want to shorten the example by:
limiting the shown depth
limiting the amount of elements in a list
For the root-element that example might look like the following:
<root>
<elements>
<element>...<element>
<element>...<element>
<element>...<element>
...
</elements>
</root>
My approach:
To generate classes from the XSD and to generate and validate the XML files I use JAXB.
But I could not figure out how to marshal a Non-Root element.
Therefore I am generating my examples with XStream.
To limit the XML-example I am trying to decorate the PrettyPrintWriter, but that seems to be quite cumbersome.
The two decorators can be seen in my answer.
I just did not expect to care about the internals of a library for such a (common?) feature.
Is there an easier way to do this? (I can also use another library than XStream, or none at all.)
*
My approach is influenced by Spring Auto Rest Docs
To limit the shown depth I created the following XStream WriterWrapper. The class can wrap for example a PrettyPrintWriter and ensures that the wrapped writer only receives the nodes above a given depth threshold.
public class RestrictedPrettyPrintWriter extends WriterWrapper {
private final ConverterLookup converterLookup;
private final int maximalDepth;
private int depth;
public RestrictedPrettyPrintWriter(HierarchicalStreamWriter sw, ConverterLookup converterLookup, int maximalDepth) {
super(sw);
this.converterLookup = converterLookup;
this.maximalDepth = maximalDepth;
}
#Override public void startNode(String name, Class clazz) {
Converter converter = this.converterLookup.lookupConverterForType(clazz);
boolean isSimpleType = converter instanceof SingleValueConverter;
_startNode(name, !isSimpleType);
}
#Override public void startNode(String name) {
_startNode(name, false);
}
#Override public void endNode() {
if (_isLessDeepThanMaximalDepth() || _isMaximalDepthReached()) {
super.endNode();
}
depth--;
}
#Override public void addAttribute(String key, String value) {
if (_isLessDeepThanMaximalDepth() || _isMaximalDepthReached()) {
super.addAttribute(key, value);
}
}
#Override public void setValue(String text) {
if (_isLessDeepThanMaximalDepth() || _isMaximalDepthReached()) {
super.setValue(text);
}
}
/**
* #param name name of the new node
* #param isComplexType indicates if the element is complex or contains a single value
*/
private void _startNode(String name, boolean isComplexType) {
depth++;
if (_isLessDeepThanMaximalDepth()) {
super.startNode(name);
} else if (_isMaximalDepthReached()) {
super.startNode(name);
/*
* set the placeholder value now
* setValue() will never be called for complex types
*/
if (isComplexType) {
super.setValue("...");
}
}
}
private boolean _isMaximalDepthReached() {
return depth == maximalDepth;
}
private boolean _isLessDeepThanMaximalDepth() {
return depth < maximalDepth;
}
}
To limit the lists, I tried, in a first attempt, to modify the XStream CollectionConverter. But this approach was not general enough because implicit lists do not use this converter.
Therefore I created another WriterWrapper which counts the consecutive occurrences of elements with the same name.
public class RestrictedCollectionWriter extends WriterWrapper {
private final int maxConsecutiveOccurences;
private int depth;
/** Contains one element per depth.
* More precisely: the current element and its parents.
*/
private Map < Integer, Elements > elements = new HashMap < > ();
public RestrictedCollectionWriter(HierarchicalStreamWriter sw, int maxConsecutiveOccurences) {
super(sw);
this.maxConsecutiveOccurences = maxConsecutiveOccurences;
}
#Override public void startNode(String name, Class clazz) {
_startNode(name);
}
#Override public void startNode(String name) {
_startNode(name);
}
#Override public void endNode() {
if (_isCurrentElementPrintable()) {
super.endNode();
}
depth--;
}
#Override public void addAttribute(String key, String value) {
if (_isCurrentElementPrintable()) {
super.addAttribute(key, value);
}
}
#Override public void setValue(String text) {
if (_isCurrentElementPrintable()) {
super.setValue(text);
}
}
/**
* #param name name of the new node
*/
private void _startNode(String name) {
depth++;
Elements currentElement = this.elements.getOrDefault(depth, new Elements());
this.elements.put(depth, currentElement);
Elements parent = this.elements.get(depth - 1);
boolean parentPrintable = parent == null ? true : parent.isPrintable();
currentElement.setName(name, parentPrintable);
if (currentElement.isPrintable()) {
super.startNode(name);
}
}
private boolean _isCurrentElementPrintable() {
Elements currentElement = this.elements.get(depth);
return currentElement.isPrintable();
}
/**
* Evaluates if an element is printable or not.
* This is based on the concurrent occurences of the element's name
* and if the parent element is printable or not.
*/
private class Elements {
private String name = "";
private int concurrentOccurences = 0;
private boolean parentPrintable;
public void setName(String name, boolean parentPrintable) {
if (this.name.equals(name)) {
concurrentOccurences++;
} else {
concurrentOccurences = 1;
}
this.name = name;
this.parentPrintable = parentPrintable;
}
public boolean isPrintable() {
return parentPrintable && concurrentOccurences <= maxConsecutiveOccurences;
}
}
}
The following listing shows, how the two classes can be used.
XStream xstream = new XStream(new StaxDriver());
StringWriter sw = new StringWriter();
PrettyPrintWriter pw = new PrettyPrintWriter(sw);
RestrictedCollectionWriter cw = new RestrictedCollectionWriter(pw, 3);
xstream.marshal(objectToMarshal, new RestrictedPrettyPrintWriter(cw, xstream.getConverterLookup(), 3));
I make a POST-type call in eclipse / java using JAX-RS
I can not handle the return in the method predictCid
This method sends the textToPredict parameter and receives a return string, how can I get this value and set it to the variable, textPredicted?
#Path("Predicao")
public class PredicaoCIDResource extends BaseResource {
#POST
#Path("predicaoCid")
public RetornoGenerico<PredicaoCidVo> predizerCid(PredicaoCidVo predicaoVo) {
System.out.print("\nentrou no método java");
RetornoGenerico<PredicaoCidVo> retorno = new RetornoGenerico<PredicaoCidVo>();
String nomeMetodo = "predicaoCid";
super.criarRetornoSucesso(nomeMetodo, retorno);
System.out.print("passou pelo super");
try {
System.out.print("\nentrou no try");
PredicaoCidVo predicaoCidVo = new PredicaoCidVo();
Response retornoPred = predictCid(predicaoVo.getTextToPredict());
System.out.print("retornou do método predict");
predicaoCidVo.setTextPredicted(retornoPred.getEntity().toString());
retorno.setRetorno(predicaoCidVo);
} catch (Exception e) {
super.trataExececao(retorno, e, nomeMetodo);
}
return retorno;
}
#POST
#Path("http://127.0.0.1:5000/predict")
#Consumes("application/x-www-form-urlencoded")
private Response predictCid(#FormParam("textToPredict") String predicaoVo) throws IOException {
System.out.print("\nentrou no método predict");
//How get te return ??? String
}
PredicaoVo:
#XmlRootElement
public class PredicaoCidVo implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2471424108047814793L;
private String textToPredict;
private String textPredicted;
public String getTextToPredict() {
return textToPredict;
}
public void setTextToPredict(String textToPredict) {
this.textToPredict = textToPredict;
}
public String getTextPredicted() {
return textPredicted;
}
public void setTextPredicted(String textPredicted) {
this.textPredicted = textPredicted;
}
}
The call is made correctly (predictCid), returns with status 200 (OK).
But, I can not return in one of the class variables PredicaoVo.
How do I make this return by filling in, for example, the textPredicted object?
The return, within the method that does the POST, is a simple string
Below, the feedback I have on SOAPUI testing:
<Response xmlns="http://app-homolog/overcare-ws/rest/Profissional/predicaoCid">
<retorno>
<textPredicted>predicao.PredicaoCidVo#3372c9d7</textPredicted>
<textToPredict null="true"/>
</retorno>
<retornoMensagem>
<dsMensagem>predicaoCid.sucesso</dsMensagem>
<dsStackTrace null="true"/>
<dsTitulo>predicaoCid.titulo</dsTitulo>
<idCamada>SUCESSO</idCamada>
</retornoMensagem>
</Response>
Who sends the return to the soapUI is the method predizerCid
In a previous post Creating a ToolTip Managed bean
I was able to create a manged bean to collect and display tooltip text with only a single lookup and store them in an Application Scope variable. This has worked great.
I am on the rather steep part of the JAVA learning curve so please forgive me.
I have another managed bean requirement to create a HashMap Application Scope but this time it needs to be of a type String, Object. The application is where I have a single 'master' database that contains most of the code, custom controls, and XPages. This Master Database will point to One or More application databases that will store the Notes Documents specific to the application in question. So I have created in the Master a series of Application Documents that specify the RepIDs of the Application, Help and Rules databases specific to the Application along with a number of other pieces of information about the Application. This should allow me to reuse the same custom control that will open the specific DB by passing it the Application Name. As an example the Master Design DB might point to "Purchasing", "Customer Complaints", "Travel Requests" etc.
The code below works to load and store the HashMap, but I am having trouble retrieving the the data.
The compiler shows two errors one at the public Object get(String key) method and the other at mapValue = this.internalMap.get(key); in the getAppRepID method I think that it is mainly syntax but not sure. I have commented the error in the code where it appears.
/**
*This Class makes the variables that define an application within Workflo!Approval
*available as an ApplicationScope variable.
*/
package ca.wfsystems.wfsAppUtils;
import lotus.domino.Base;
import lotus.domino.Session;
import lotus.domino.Database;
import lotus.domino.View;
import lotus.domino.NotesException;
import lotus.domino.ViewColumn;
import lotus.domino.ViewEntry;
import lotus.domino.ViewEntryCollection;
import lotus.domino.Name;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import com.ibm.xsp.extlib.util.ExtLibUtil;
/**
* #author Bill Fox Workflo Systems WFSystems.ca
* July 2014
* This class is provided as part of the Workflo!Approval Product
* and can be reused within this application.
* If copied to a different application please retain this attribution.
*
*/
public abstract class ApplicationUtils implements Serializable, Map<String, Object> {
private static final long serialVersionUID = 1L;
private Session s;
private Name serverName;
private String repID;
private String thisKey;
private ViewEntryCollection formVECol;
private Vector formNames;
private Database thisDB;
private Database appDB;
private View appView;
private View formView;
private ViewEntry formVE;
private ViewEntry tFormVE;
private ViewEntry ve;
private ViewEntry tVE;
private ViewEntryCollection veCol;
private final Map<String, Object> internalMap = new HashMap<String, Object>();
public ApplicationUtils() {
this.populateMap(internalMap);
}
private void populateMap(Map<String, Object> theMap) {
try{
s = ExtLibUtil.getCurrentSession();
//serverName = s.createName(s.getServerName());
thisDB = s.getCurrentDatabase();
appView = thisDB.getView("vwWFSApplications");
veCol = appView.getAllEntries();
ve = veCol.getFirstEntry();
ViewEntry tVE = null;
while (ve != null) {
rtnValue mapValue = new rtnValue();
tVE = veCol.getNextEntry(ve);
Vector colVal = ve.getColumnValues();
thisKey = colVal.get(0).toString();
mapValue.setRepID(colVal.get(2).toString());
// ...... load the rest of the values .......
theMap.put(thisKey, mapValue);
recycleObjects(ve);
ve = tVE;
}
}catch(NotesException e){
System.out.println(e.toString());
}finally{
recycleObjects(ve, veCol, appView, tVE);
}
}
public class rtnValue{
private String RepID;
private String HelpRepID;
private String RuleRepID;
private Vector FormNames;
public String getRepID() {
return RepID;
}
public void setRepID(String repID) {
RepID = repID;
}
public String getHelpRepID() {
return HelpRepID;
}
public void setHelpRepID(String helpRepID) {
HelpRepID = helpRepID;
}
public String getRuleRepID() {
return RuleRepID;
}
public void setRuleRepID(String ruleRepID) {
RuleRepID = ruleRepID;
}
public Vector getFormNames() {
return FormNames;
}
public void setFormNames(Vector formNames) {
FormNames = formNames;
}
}
public void clear() {
this.internalMap.clear();
this.populateMap(this.internalMap);
}
public boolean containsKey(Object key) {
return this.internalMap.containsKey(key);
}
public boolean containsValue(Object value) {
return this.internalMap.containsValue(value);
}
public Set<java.util.Map.Entry<String, Object>> entrySet() {
return this.internalMap.entrySet();
}
public Object get(String key) {
//error on Object get Method must return a result of type Object
try {
if (this.internalMap.containsKey(key)) {
return this.internalMap.get(key);
}
} catch (Exception e) {
System.out.println(e.toString());
rtnValue newMap = new rtnValue();
return newMap;
}
}
public boolean isEmpty() {
return this.internalMap.isEmpty();
}
public Set<String> keySet() {
return this.internalMap.keySet();
}
public Object put(String key, Object value) {
return this.internalMap.put(key, value);
}
public Object remove(Object key) {
return this.internalMap.remove(key);
}
public int size() {
return this.internalMap.size();
}
public Collection<Object> values() {
return this.internalMap.values();
}
public void putAll(Map<? extends String, ? extends Object> m) {
this.internalMap.putAll(m);
}
public String getAppRepID(String key){
/*get the Replica Id of the application database
* not sure this is the correct way to call this
*/
rtnValue mapValue = new rtnValue();
mapValue = this.internalMap.get(key);
//error on line above Type Mismatch: can not convert Object to ApplicationUtils.rtnValue
String repID = mapValue.getRepID();
}
public static void recycleObjects(Object... args) {
for (Object o : args) {
if (o != null) {
if (o instanceof Base) {
try {
((Base) o).recycle();
} catch (Throwable t) {
// who cares?
}
}
}
}
}
}
For the get() method, the way I handle that kind of situation is create a variable of the correct data type as null, in my try/catch set the variable, and at the end return the variable. So:
Object retVal = null;
try....
return retVal;
For the other error, if you right-click on the error marker, it might give you the opportunity to cast the variable to rtnValue, so:
mapValue = (rtnValue) this.internalMap.get(key)
If you haven't got it, Head First Java was a useful book for getting my head around some Java concepts. It's also worth downloading the FindBugs plugin for Domino Designer from OpenNTF. It will identify errors as well as bad practices. Just ignore the errors in the "local" package!
The problem is that there is an execution path that do not return nothing
public Object get(String key) {
//error on Object get Method must return a result of type Object
try {
if (this.internalMap.containsKey(key)) { // false
return this.internalMap.get(key);
}
} catch (Exception e) {
System.out.println(e.toString());
rtnValue newMap = new rtnValue();
return newMap;
}
}
if key is not present in the internalMap, nothing is thrown, then that method do not return anything.
To fix the problem, return the newMap at the end.
public Object get(String key) {
//error on Object get Method must return a result of type Object
try {
if (this.internalMap.containsKey(key)) {
return this.internalMap.get(key);
}
} catch (Exception e) {
System.out.println(e.toString());
}
rtnValue newMap = new rtnValue();
return newMap;
}
You can inline the return to save the allocation (which is what the compiler will do anyway). I didn't do it just to make it clear in the example.
But still you have a compiler error in getAppRepID method. You are expecting a rtnValue but you send back an Object. You must cast there.
The appropriate way to handle this is, if you know that all values are of a given type, create the map with the proper type.
Have you tried making your internalMap a map of rtnValue instances (so )?
Our data model is separated into schemas on two databases. The schemas are used in isolation except for a few single-key relationships that bridge between the two. There are no write transactions that will span both databases.
Similar to this question Doing a join over 2 tables in different databases using Hibernate, we want to use Hibernate to handle joining the entities. We cannot use the database solution (Federated views on DB2).
We have set up Hibernate with two separate database configurations (Doctor and Patient), which works perfectly when using DAOs to explicitly access a particular session.
We want to use Hibernate to automatically retrieve the entity when we call DoctorBO.getExam().getPatient() Where examination contains an id pointing to the Patient table on the other database.
One way I've tried doing this is using a custom UserType:
public class DistributedUserType implements UserType, ParameterizedType
{
public static final String CLASS = "CLASS";
public static final String SESSION = "SESSION";
private Class<? extends DistributedEntity> returnedClass;
private String session;
/** {#inheritDoc} */
#Override
public int[] sqlTypes()
{
// The column will only be the id
return new int[] { java.sql.Types.BIGINT };
}
/** {#inheritDoc} */
#Override
public Class<? extends DistributedEntity> returnedClass()
{
// Set by typedef parameter
return returnedClass;
}
/** {#inheritDoc} */
#Override
public boolean equals(Object x, Object y) throws HibernateException
{
if (x == y)
{
return true;
}
if ((x == null) || (y == null))
{
return false;
}
Long xId = ((DistributedEntity) x).getId();
Long yId = ((DistributedEntity) y).getId();
if (xId.equals(yId))
{
return true;
}
else
{
return false;
}
}
/** {#inheritDoc} */
#Override
public int hashCode(Object x) throws HibernateException
{
assert (x != null);
return x.hashCode();
}
/** {#inheritDoc} */
#Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException
{
Long id = rs.getLong(names[0]);
return HibernateUtils.getSession(session).get(returnedClass, id);
}
/** {#inheritDoc} */
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException
{
DistributedEntity de = (DistributedEntity) value;
st.setLong(index, de.getId());
}
/** {#inheritDoc} */
#Override
public Object deepCopy(Object value) throws HibernateException
{
return value;
}
/** {#inheritDoc} */
#Override
public boolean isMutable()
{
return false;
}
/** {#inheritDoc} */
#Override
public Serializable disassemble(Object value) throws HibernateException
{
return (Serializable) value;
}
/** {#inheritDoc} */
#Override
public Object assemble(Serializable cached, Object owner) throws HibernateException
{
return cached;
}
/** {#inheritDoc} */
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException
{
return original;
}
/** {#inheritDoc} */
#Override
public void setParameterValues(Properties parameters)
{
String clazz = (String) parameters.get(CLASS);
try
{
returnedClass = ReflectHelper.classForName(clazz);
}
catch (ClassNotFoundException e)
{
throw new IllegalArgumentException("Class: " + clazz + " is not a known class type.");
}
session = (String) parameters.get(SESSION);
}
}
Which would then be used:
#TypeDef(name = "testUserType", typeClass = DistributedUserType.class, parameters = {
#Parameter(name = DistributedUserType.CLASS, value = PatientBO.CLASSNAME),
#Parameter(name = DistributedUserType.SESSION, value = HibernateUtils.PATIENT_SESS) })
#Type(type = "testUserType")
#Column(name = "PATIENT_ID")
private PatientBO patient;
The UserType works - the data is loaded correctly with only the Id of the field persisted to the database. I have tested very simple examples of doctor.getExam().getPatient() and doctor.getExam().setPatient() and both seem to work great, however I think this is a terrible hack and I do not have adequate knowledge of Hibernate to know if this is safe to use.
Is there a better way to achieve what we want? Is the way I've described here adequate, or will it cause difficulties in the future?
I don't think it's a good idea. You're trying to make "as if" everything was in a single database, whereas it's not the case. And you make "as if" there was a real toOne association between an exam and a patient, although it's not a real association.
Although you are conscious of this fact, other or future developers won't necessarily be, and will wonder why it's not possible to make a query such as
select e from Exam e left join fetch e.patient
or
select e from Exam e where e.patient.name like 'Smith%'
In short, your pseudo-association only fulfills a very small part of the contract a regular association offers, and this will, IMO, cause more confusion than comfort.
Nothing stops you from having a utility method like
Patient getExamPatient(Exam e)
that does the same thing, but makes it clear that there is no real asociation between both entities.