I have a single table called Tags that stores a "Tag" as a row, regardless of what specific subclass they represent. Some rows represent modbus tags, some snmp, some other protocols. All classes inheriting from Tag store their data in this one table, and unused columns simply contain null values.
At the moment, I have DAO methods like, getAllModBusTags() which contains an instruction mapToBean(ModBusTag.class). Eventually all of the subclasses of Tag are fetched from the database (one fetch per protocol) and then added to an ArrayList of the supertype Tag.
My question is, is there a simple means with Jdbi to perform conditional mapping of rows so that if a row contains a specific value, it is mapped to ModBusTag.class but if a row contains a different value it is mapped to SNMPTag.class, and so on and so forth?
My end goal is to have a single select statement that fetches every tag from the database, automaps to the correct bean on a row by row basis and then stores all of these subclass beans in a List of the supertype Tag.
Example Method for Single Type:
#Override
public List<SNMPTag> getSNMPTags(){
try(Handle handle = daoFactory.getDataSourceController().open()) {
return handle.createQuery("SELECT * FROM dbo.Tags WHERE Active = 1 AND Protocol = 'SNMP'")
.mapToBean(SNMPTag.class)
.list();
}
catch(Exception e){
if(sysconfig.getVerbose()){ e.printStackTrace(); }
}
return null;
}
Some bad pseudocode to indicate what I want to do:
#Override
public List<Tag> getAllTags(){
try(Handle handle = daoFactory.getDataSourceController().open()) {
return handle.createQuery("SELECT * FROM dbo.Tags WHERE Active = 1")
.mapRows(row -> row.Protocol.equals("SNMP").mapToBean(SNMPTag.class)
.mapRows(row -> row.Protocol.equals("ModBus").mapToBean(ModBusTag.class)
//etc
.list();
}
catch(Exception e){
if(sysconfig.getVerbose()){ e.printStackTrace(); }
}
return null;
}
You can use RowMapper with some amount of custom code to achieve what you need, we successfully use such approach in our project. Here is simplified general example of this technique:
public class PolymorphicRowMapper implements RowMapper<Parent> {
#Override
public Parent map(ResultSet rs, StatementContext ctx) throws SQLException {
Type type = Type.valueOf(rs.getString("type"));
if (type == Type.A) {
return mapTo(rs, ctx, ChildA.class);
} else if (type == Type.B) {
return mapTo(rs, ctx, ChildB.class);
}
throw new IllegalStateException("Could not resolve mapping strategy for object");
}
private static <T extends Parent> T mapTo(
ResultSet rs,
StatementContext ctx,
Class<T> targetClass
) throws SQLException {
return ctx.getConfig().get(Mappers.class)
.findFor(targetClass)
.orElseThrow(() ->
new NoSuchMapperException(String.format("No mapper registered for %s class", targetClass))
)
.map(rs, ctx);
}
}
public static void main(String[] args) {
var jdbi = Jdbi.create("...")
.registerRowMapper(BeanMapper.factory(ChildA.class))
.registerRowMapper(BeanMapper.factory(ChildB.class));
try (Handle handle = jdbi.open()) {
handle.createQuery("SELECT * FROM table")
.map(new PolymorphicRowMapper());
}
}
public enum Type {
A, B
}
public abstract class Parent {
final Type type;
protected Parent(final Type type) {
this.type = type;
}
}
public class ChildA extends Parent {
public ChildA() {
super(Type.A);
}
}
public class ChildB extends Parent {
public ChildB() {
super(Type.B);
}
}
Related
I have two ComboBoxes:
final ComboBox<MainCategory> comboBoxMainCategory = new ComboBox<>();
final ComboBox<SubCategory> comboBoxSubCategory = new ComboBox<>();
Depending on the value chosen in comboBoxMainCategory, the comboBoxSubCategory should be populated with the corresponding enum.
public enum MainCategory { // extra enum class
EUROPE("Europe"),
USA("USA");
}
public enum SubCategoryEurope { // extra enum class
GERMANY("Germany"),
FRANCE("France");
}
public enum SubCategoryUSA {
COLORADO("Colorado"),
CALIFORNIA("California");
}
If "Europe" is chosen for comboBoxMainCategory, comboBoxSubCategory should be populated with SubCategoryEurope. If "USA", with SubCategoryUSA.
How do you achieve this?
Here's my code:
final ComboBox<MainCategory> comboBoxMainCategory = new ComboBox<();
final ComboBox<SubCategory> comboBoxSubCategory = new ComboBox<>();
comboBoxMainCategory.valueProperty().addListener((obs, oldValue,
newValue) ->
{
if (newValue == null) { // newValue: Europe || USA
comboBoxSubCategory.getItems().clear();
comboBoxSubCategory.setDisable(true);
} else if (newValue.equals(MainCategory.EUROPE)) {
comboBoxSubCategory.setItems(FXCollections.observableArrayList(SubCategoryEurope.values()));
comboBoxSubCategory.setDisable(false);
} else {
comboBoxSubCategory.setItems(FXCollections.observableArrayList(SubCategoryUSA.values()));
comboBoxSubCategory.setDisable(false);}
});
Problem is, because comboBoxSubCategory is "SubCategory", there is a type error if it is populated with 'SubCategoryEurope' or 'SubCategoryUSA'.
What is the best way to solve this? Sorry if it's a silly question, I'm new to JavaFx.
Thanks a lot!
I wouldn't use enums at all, since this doesn't allow for data manipulation without recompiling. If you insist on using enums though, you need to use Object or a interface implemented with both subcategory enum types as parameter type for comboBoxSubCategory:
comboBoxMainCategory.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue == null) { // newValue: Europe || USA
comboBoxSubCategory.getItems().clear();
comboBoxSubCategory.setDisable(true);
} else {
comboBoxSubCategory.setDisable(false);
List<? extends Object> list;
switch (newValue) {
case EUROPE:
list = Arrays.asList(SubCategoryEurope.values());
break;
default:
list = Arrays.asList(SubCategoryUSA.values());
break;
}
comboBoxSubCategory.getItems().setAll(list);
}
});
The better approach would be using a Map<String, List<String>> to store the data:
Map<String, List<String>> data = new HashMap<>();
data.put("EUROPE", Arrays.asList("GERMANY", "FRANCE"));
data.put("USA", Arrays.asList("COLORADO", "CALIFORNIA"));
comboBoxMainCategory.valueProperty().addListener((obs, oldValue, newValue) -> {
List<String> list = data.get(newValue);
if (list != null) {
comboBoxSubCategory.setDisable(false);
comboBoxSubCategory.getItems().setAll(list);
} else {
comboBoxSubCategory.getItems().clear();
comboBoxSubCategory.setDisable(true);
}
});
Just for fun (and to flesh out my comments): a more versatile approach than those in the other answers is to move away the focus of interest from the concrete nature of the backing data to a more general solution of the use-case at hand. The drawback of letting the UI implement special cases is always the same - you have to do it over and over again for each special UI and each special data type. The way out is always the same, too: implement a Model that takes over the general aspect and re-use that in concrete UI/data contexts.
The general aspects here are:
there's list of items with each having a list of dependent objects (same or different type)
this (let's call it root) list of items is shown in a control
from root list, a single item can be chosen (aka: selected)
another control should show the dependents of the root
The general approach is to have a Model that
manages list of items
has the notion of one of those items as selected (or current or active or ..)
manages a list of dependent items that always is the dependent list of the selected item
its state (root items, current item, dependent items) is exposed as properties
The advantages of such a Model are
can be formally and rigorouly tested, so using code can rely on its proper functioning
it's re-usable for any data context
it's re-usable for many controls
usage is pretty simple by binding
In the example below, the Model is named RelationModel which expects root items of type RelationProvider (which allows access to a list of dependents, it's one option, could just as well use f.i. a Function to build the dependents). It is used once with a plain Map of String/list and once with enums of Continents/Countries, each very simple to implement. Note that the resulting UI is blissfully unaware of the nature of the data, implemented solely against the model.
Naturally, not production grade, in particular, not formally tested and the model with just the barest functionality :)
public class CombosWithCategories extends Application {
public interface RelationProvider<T> {
default ObservableList<T> getRelations() {
return emptyObservableList();
};
}
/**
* A model that manages a list of RelationProviders and has the notion
* of a current relationProvider with relations (it's a kind-of selectionModel).
*
* <T> the type of elements in the list of relations
*/
public static class RelationModel<T> {
/**
* all relationProviders managed by this model
*/
private ListProperty<RelationProvider<T>> relationProviders;
/**
* The owner of the relations. Must be contained in the providers managed
* by this model.
*/
private ObjectProperty<RelationProvider<T>> relationProvider;
private ListProperty<T> relations;
public RelationModel() {
initProperties();
}
/**
* The RelationProviders managed by the model.
*/
public ListProperty<RelationProvider<T>> relationProvidersProperty() {
return relationProviders;
}
/**
* The RelationProvider that manages the current relations.
*/
public ObjectProperty<RelationProvider<T>> relationProviderProperty() {
return relationProvider;
}
public RelationProvider<T> getRelationProvider() {
return relationProviderProperty().get();
}
public ListProperty<T> relations() {
return relations;
}
/**
* Callback from invalidation of current relationProvider.
* Implemented to update relations.
*/
protected void relationProviderInvalidated() {
RelationProvider<T> value = getRelationProvider();
relations().set(value != null ? value.getRelations() : emptyObservableList());
}
/**
* Creates and wires all properties.
*/
private void initProperties() {
relationProviders = new SimpleListProperty<>(this, "relationProviders", observableArrayList());
relationProvider = new SimpleObjectProperty<>(this, "relationProvider") {
#Override
protected void invalidated() {
// todo: don't accept providers that are not in the list
relationProviderInvalidated();
}
};
relations = new SimpleListProperty<>(this, "relations");
relationProviderInvalidated();
}
}
/**
* Implement the ui against a RelationModel. Here we create
* the same UI with a model backed by enums or a Map, respectively
*/
private Parent createContent() {
TabPane tabPane = new TabPane(
new Tab("Enums", createRelationUI(createEnumRelationModel())),
new Tab("Manual map", createRelationUI(createMapRelationModel()))
);
return new BorderPane(tabPane);
}
/**
* Common factory for UI: creates and returns a Parent that
* contains two combo's configured to use the model.
*/
protected <T> Parent createRelationUI(RelationModel<T> model) {
ComboBox<RelationProvider<T>> providers = new ComboBox<>();
providers.itemsProperty().bind(model.relationProvidersProperty());
providers.valueProperty().bindBidirectional(model.relationProviderProperty());
ComboBox<T> relations = new ComboBox<>();
relations.itemsProperty().bind(model.relations());
relations.valueProperty().addListener((src, ov, nv) -> {
LOG.info("relation changed: " + nv);
});
return new VBox(10, providers, relations);
}
// ------------- manual with maps
/**
* On-the-fly creation of a RelationModel using a backing map.
*/
protected RelationModel<String> createMapRelationModel() {
RelationModel<String> model = new RelationModel<>();
Map<String, ObservableList<String>> data = new HashMap<>();
data.put("EUROPE", observableArrayList("GERMANY", "FRANCE"));
data.put("AMERICA", observableArrayList("MEXICO", "USA"));
for (String key: data.keySet()) {
model.relationProvidersProperty().add(new RelationProvider<String>() {
#Override
public ObservableList<String> getRelations() {
return data.get(key);
}
#Override
public String toString() {
return key;
}
});
}
return model;
}
//-------------------- enum
/**
* RelationModel using Enums.
*/
protected RelationModel<Object> createEnumRelationModel() {
RelationModel<Object> model = new RelationModel<Object>();
model.relationProvidersProperty().setAll(Continent.values());
return model;
}
public enum EuropeanCountry {
FRANCE, GERMANY;
}
public enum AmericanCountry {
MEXICO, CANADA, USA;
}
public enum Continent implements RelationProvider<Object> {
AMERICA(AmericanCountry.values()),
EUROPE(EuropeanCountry.values())
;
ObservableList<Object> subs;
private Continent(Object[] subs) {
this.subs = FXCollections.observableArrayList(subs);
}
#Override
public ObservableList<Object> getRelations() {
return FXCollections.unmodifiableObservableList(subs);
}
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(CombosWithCategories.class.getName());
}
Create a generic placeholder interface
public interface EnumPlaceHolder<E extends Enum<?>> {
public abstract String getDisplayValue();
public abstract E getEnum();
}
Create an implementation for all your enums. For example
public class EuropePlaceholder implements EnumPlaceHolder<Europe> {
private final Europe value;
public EuropePlaceholder(Europe pValue){
value = pValue;
}
#Override
public String getDisplayValue() {
// here you create a user-friendly version of your enum for display
return value.toString();
}
#Override
public Europe getEnum() {
return value;
}
}
Then change the type of your ComboBox<Enum> to ComboBox<EnumPlaceholder<?>> and you can add any of your implemented EnumPlaceholders to it. When retrieving the selected item you can check which one is contained via instance check
EnumPlaceholder<?> selectedItem = ...;
if(selectedItem instanceof EuropePlaceholder){
Europe selectedEuropeEnum = (Europe) selectedItem.getEnum();
} else if(....){
// check with else if for your other enums
}
And to display your enum in your combobox you call the getDisplayValue() of the EnumPlaceholder and show the returned String in your cell :)
EDIT
Tho in general i have to agree with fabians answer. You shouldn't use enums for a construct like this. Rather use a Map<> or a List<> with appropriate content and structure.
Is there a better way to deal with an instanciation of an object (Product) which depends upon another object type (Condition) than using if-else paired with instanceof as the following code shows?
import java.util.ArrayList;
import java.util.List;
abstract class AbstractProduct {
private AbstractCondition condition;
public AbstractProduct(AbstractCondition condition) {
this.condition = condition;
}
public abstract void doSomething();
}
class ProductA extends AbstractProduct {
AbstractCondition condition;
public ProductA(AbstractCondition condition) {
super(condition);
}
#Override
public void doSomething() {
System.out.println("I'm Product A");
}
}
class ProductB extends AbstractProduct {
public ProductB(AbstractCondition condition) {
super(condition);
}
#Override
public void doSomething() {
System.out.println("I'm Product B");
}
}
class AbstractCondition { }
class ConditionA extends AbstractCondition { }
class ConditionB extends AbstractCondition { }
public class Try {
public static void main(String[] args) {
List<AbstractCondition> conditions = new ArrayList<AbstractCondition>();
List<AbstractProduct> products = new ArrayList<AbstractProduct>();
conditions.add(new ConditionA());
conditions.add(new ConditionB());
conditions.add(new ConditionB());
conditions.add(new ConditionA());
for (AbstractCondition c : conditions) {
tryDoSomething(c);
}
}
public static void tryDoSomething(AbstractCondition condition) {
AbstractProduct product = null;
if (condition instanceof ConditionA) {
product = new ProductA(condition);
} else if (condition instanceof ConditionB) {
product = new ProductB(condition);
}
product.doSomething();
}
}
The difference with the code above of my real code is: I have NO direct control over AbstractCondition and its subtypes (as they are in a library), but the creation of a concrete subtype of AbstractProduct depends on the concrete condition.
My goal being: try to avoid the if-else code smell in tryDoSomething().
I would also like to avoid reflection because it feels like cheating and I do think it's not an elegant, clean and readable solution.
In other words, I would like to tackle the problem just with good OOP principles (e.g. exploiting polymorphism) and pheraps some design patterns (which apparently I don't know in this specific case).
Since you can't edit the original objects, you need to create a static map from condition type to product type:
private static HashMap< Class<? extends AbstractCondition>,
Class<? extends AbstractProduct>
> conditionToProduct;`
Fill it in static initialization with the pairs of Condition,Product:
static {
conditionToProduct.put(ConditionA.class, ProductA.class);
...
}
and in runtime just query the map:
Class<? extends AbstractProduct> productClass = conditionToProduct.get(condition.getClass());
productClass.newInstance();
AbstractCondition needs to know either the type or how to construct a product.
So add one of the following functions to AbstractCondition
Class<? extends AbstractProduct> getProductClass()
or
AbstractProduct createProduct()
You should create a Factory class to help you with that then.
interface IFactoryProduct{
AbstractProduct getProduct(AbstractCondition condition) throws Exception;
}
This will be your interface, just need to implement it like this.
class FactoryProduct implements IFactoryProduct{
public AbstractProduct getProduct(AbstractCondition condition) throws Exception{
return (AbstractProduct)getClass().getMethod("getProduct", condition.getClass()).invoke(this, condition);
}
public ProductA getProduct(ConditionA condition){
return new ProductA();
}
public ProductB getProduct(ConditionB condition){
return new ProductB();
}
}
Using the reflexion to redirect with the correct method will do the trick. this is upgradable for subclassed if you want.
EDIT:
Some example :
List<AbstractCondition> list = new ArrayList<AbstractCondition>();
list.add(new ConditionA());
list.add(new ConditionB());
for(AbstractCondition c : list){
try {
System.out.println(f.getProduct(c));
} catch (Exception ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
labo.ProductA#c17164
labo.ProductB#1fb8ee3
A more complexe reflexion version allowing a subclass to be received :
public AbstractProduct getProduct(AbstractCondition condition) throws Exception{
Method m = getMethodFor(condition.getClass());
if(m == null )
throw new Exception("No method for this condition " + condition.getClass().getSimpleName());
else
return (AbstractProduct) m.invoke(this, condition);
}
private Method getMethodFor(Class<? extends AbstractCondition> clazz ) throws Exception{
try {
return getClass().getMethod("getProduct", clazz);
} catch (NoSuchMethodException ex) {
if(clazz.getSuperclass() != AbstractCondition.class){
return getMethodFor((Class<? extends AbstractCondition>)clazz.getSuperclass());
}
return null;
}
}
This allows me to send ConditionC extending ConditionB to build the same product has ConditionB would. Interesting for complexe heritage.
I have an entity which has some BIT fields into the database:
editable
needs_review
active
These fields are mapped against boolean fields in its Java class using Hibernate 3.6.9 version. That forces me to write an interface method for each List of entities I want to get:
List<Entity> listEditables();
List<Entity> listReviewNeeded();
List<Entity> listActives();
Or write a general interface method to achieve a combination of them:
List<Entity> listEntities(boolean editables, boolean reviewNeeded, boolean actives);
That second choice looks greater, but if I add another field in the future there will be a need to modify the interface itself (and every line of code coupled to it).
So I decided I can express it as an enumeration Set:
public enum EntityType{
EDITABLE, REVIEW_NEEDED, ACTIVE
}
//That way there's no need to change interface method's signature
List<Entity> listEntities(Set<EntityType> requiredTypes);
It makes sense that being an enumeration match what I want to achieve, the Entity type itself should have its own Set<EntityType>:
public class Entity{
Set<EntityType> entityTypes;
}
However instead of that I have the mapped booleans which logically match that Set. Then my question, is there any way to map Set<EntityType> entityTypes in hibernate based in that BIT fields or do I have to manage that logic myself having them as boolean?
UPDATE
Having them mapped as a Set implies the possibility of querying for a List using an in clause, if not it would imply an extra step for conversion between my controller and model codes.
Set<EntityType> typesSet = Sets.newHashSet(EntityType.EDITABLE, EntityType.REVIEW_NEEDED);
//Obtains a list of every single entity which is EDITABLE or REVIEW_NEEDED
session.createCriteria(Entity.class).addRestriction(Restrictions.in("entityTypes",typeSet)).list();
I think I have a solution for you. What you are interested in is a CompositeUserType.
As an example lets use a InetAddress composite user type I wrote lately to map a 128bit IPv6 Address / IPv4Address object to two 64bit long properties inside a user account entity.
The signupIp:InetAddress is mapped towards two columns (there is no column count limit or alike) using:
#Columns(columns = {#Column(name = "ip_low", nullable = true), #Column(name = "ip_high", nullable = true)})
private InetAddress signupIp;
And the interesting part of the implementation looks like this:
public class InetAddressUserType implements CompositeUserType {
#Override
public String[] getPropertyNames() {
return new String [] {"ipLow", "ipHigh"};
}
#Override
public Type[] getPropertyTypes() {
return new Type [] { LongType.INSTANCE, LongType.INSTANCE};
}
#Override
public Object getPropertyValue(Object component, int property) throws HibernateException {
if(component != null)
return toLong((InetAddress)component)[property];
else
return null;
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if(value != null) {
long [] longs = toLong((InetAddress)value);
st.setLong(index, longs[0]);
st.setLong(index + 1, longs[1]);
}
else {
st.setNull(index, LongType.INSTANCE.sqlType());
st.setNull(index + 1, LongType.INSTANCE.sqlType());
}
}
#Override
public void setPropertyValue(Object component, int property, Object value)
throws HibernateException {
throw new RuntimeException("This object is immutable");
}
#Override
public Class<?> returnedClass() {
return InetAddress.class;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
return x != null ? x.equals(y) : null == y;
}
#Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Long ipLow = rs.getLong(names[0]);
if(!rs.wasNull()) {
Long ipHigh = rs.getLong(names[1]);
try {
return fromLong(new long [] {ipLow, ipHigh});
} catch (UnknownHostException e) {
throw new HibernateException("Failed to get InetAddress: ip = " + ipHigh + " + " + ipLow, e);
}
}
else
return null;
}
#Override
public Object deepCopy(Object value) throws HibernateException {
if(value != null)
try {
return InetAddress.getByAddress(((InetAddress)value).getAddress());
} catch (UnknownHostException e) {
throw new RuntimeException("Impossible Exception: " + e.getMessage(), e);
}
else
return null;
}
#Override
public boolean isMutable() {
return false;
}
...
}
Note that I flexibly switch between Inet4Address and Inet6Address instances depending on the values of ipLow and ipHigh. The composite is marked as immutable and you need to check the documentation and the examples in the Hibernate source code (build in composite user types).
In a similar way you can map your meaningful bit properties. You can query those bits by using a single Restriction.eq refering to your EnumType. You can use the equals method to check the properties object. And if you need to refer to a special mapped bit you can use the dot notation like in signupIp.ipLow to refer to the ipLow property/column.
I guess this is what you are looking for.
Update:
In the end it boils down to define the right order of your properties. Hibernate will always use integer index values to access each property:
//immutable for simplicity
class Status {
private final boolean editable;
private final boolean needsReview;
private final boolean active;
//... constructor + isEditable etc..
}
In your StatusCompositeType class:
public String[] getPropertyNames() {
return new String [] {"editable", "needsReview", "active"};
}
public Type[] getPropertyTypes() {
return new Type [] { BooleanType.INSTANCE, LongType.INSTANCE};
}
public Object getPropertyValue(Object component, int property) throws HibernateException {
if(component != null) {
Status status = (Status)component;
switch(property) {
case 1: return status.isEditable();
case 2: return status.isReviewNeeded();
case 3: return status.isActive();
default: throw new IllegalArgumentException();
}
}
else
return null; //all columns can be set to null if you allow a entity to have a null status.
}
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if(value != null) {
Status status = (Status)value;
st.setBoolean(index, status.isEditable());
st.setBoolean(index + 1, status.isReviewNeeded());
st.setBoolean(index + 2, status.isActive());
}
else {
st.setNull(index, BooleanType.INSTANCE.sqlType());
st.setNull(index + 1, BooleanType.INSTANCE.sqlType());
st.setNull(index + 2, BooleanType.INSTANCE.sqlType());
}
}
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Boolean isEditable = rs.getBoolean(names[0]);
if(!rs.wasNull()) {
Boolean isReviewNeeded = rs.getBoolean(names[1]);
Boolean isActive = rs.getBoolean(names[2]);
return new Status(isEditable, isReviewNeeded, isActive);
}
else
return null;
}
The rest is straight forward. Remember to implement equals and hashcode for the user type and add the type to the configuration before you create your sessionFactory.
Once you have everything in place you can create a criteria search and use:
//search for any elements that have a status of editable, no reviewNeeded and is not active (true false false).
criteria.add(Restrictions.eq("status", new Status(true, false, false));
Now your listEntities method may become either: listEntities(Status status) or listEntities(boolean editable, boolean reviewNeeded, boolean isActive).
If you need additional information just check the CompositeType and BasicType implementations Hibernate provides within its own sourcecode (look for implementors of CompositeType and BasicType). Understanding those helps alot to use and learn this intermediate level knowledge of Hibernate.
After some brainstorming, I've gone to a workaround which I consider the second best one being imposible to map an enum for the booleans in Hibernate. This is how I have my Entity class looks now:
public class Entity{
private boolean editable;
private boolean needsReview;
private boolean active;
//getters and setters
}
My listing method is implemented as this:
public List<Entity> listEntities(Set<EntityType> requiredTypes){
Criteria cri = session.createCriteria(Entity.class);
if (requiredTypes.contains(EntityType.EDITABLE)){
cri.addRestriction(Restrictions.eq("editable",true));
}
if (requiredTypes.contains(EntityType.NEEDS_REVIEW)){
cri.addRestriction(Restrictions.eq("needsReview",true));
}
if (requiredTypes.contains(EntityType.ACTIVE)){
cri.addRestriction(Restrictions.eq("active",true));
}
return cri.list();
}
Not bad, but don't know if it's the only way to go with that!
I don't think hibernate provides a way to manage the mappings the way you're describing. You can create your own UserType (https://community.jboss.org/wiki/Java5EnumUserType) but every time you add a new enum value you will have to change the logic in the UserType to map the new field as well.
The alternative will be to convert this into a one to many relationship. Your point is basically that if you want to add more fields you will have to change the signature of listEntities but also you will have to modify your table.
So, instead you can create a table that will contain your entity types and have a #OneToMany` relationship to it from your entity. For example:
Define your flags as required:
public enum Flags {
EDITABLE, REVIEW_NEEDED, ACTIVE
}
Create a one-to-many relationship to EntityType:
#Entity
#Table( name="entity" )
public class Entity implements Serializable {
#OneToMany(mappedBy = "entity")
public Set<EntityType> getEntityTypes() {
return entityTypes;
}
And a many-to-one to Entity:
#Entity
#Table( name="entityType" )
public class EntityType implements Serializable {
#Id
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ENTITY_ID")
private Entity entity;
#Enumerated(EnumType.STRING)
private Flag entityType;
...
}
PD: Please note the code is just an example and is not complete or tested.
I've got an Object in Java representing the contents of a database, like so:
public Database {
int varA;
String varB;
double varC;
}
Now I'm trying to select and order certain elements for forther processing, but I want to make it configurable, so I created an enum which represents all attributes of the object like
public enum Contents {
VarA,
VarB,
VarC;
}
So now when I create a selection like
Contents[] select = { Contents.VarC, Contents.VarB };
i want to generate a List of String values representing the actual database contents from this. Now the only Implementation i could think of is switching for each entry in the selection, with has a pretty ugly quadratic complexity...
public List<String> switchIT(Database db, Contents[] select) {
List<String> results = new ArrayList<String>();
for (Contents s : select) {
switch(s) {
case VarA:
results.add(db.varA.toString());
break;
//go on...
}
}
return results;
}
is there a more direct way to map between enum and dynamic object values?
Or in more general terms: What is the best way to select values from an object dynamically?
Use the power of Java enums, which are fully-fledged classes.
public enum Contents {
VarA { public String get(Database d) { return d.getVarA(); } },
VarB { public String get(Database d) { return d.getVarB(); } },
VarC { public String get(Database d) { return d.getVarC(); } };
public String get(Database d) { return ""; }
}
Your client code then becomes
public List<String> switchIT(Database db, Contents[] select) {
List<String> results = new ArrayList<String>();
for (Contents s : select) results.add(s.get(db));
return results;
}
A more concise, but slower, solution would be to use a single implementation of get based on reflection and use the name of the enum member to generate the appropriate getter name:
public enum Contents {
VarA, VarB, VarC;
private final Method getter;
private Contents() {
try {
this.getter = Database.class.getMethod("get"+name());
} catch (Exception e) { throw new RuntimeException(e); }
}
public String get(Database d) {
try {
return (String) getter.invoke(d);
} catch (Exception e) { throw new RuntimeException(e); }
}
}
Could you guys please help me find where I made a mistake ?
I switched from SimpleBeanEditorDriver to RequestFactoryEditorDriver and my code no longer saves full graph even though with() method is called. But it correctly loads full graph in the constructor.
Could it be caused by circular reference between OrganizationProxy and PersonProxy ? I don't know what else to think :( It worked with SimpleBeanEditorDriver though.
Below is my client code. Let me know if you want me to add sources of proxies to this question (or you can see them here).
public class NewOrderView extends Composite
{
interface Binder extends UiBinder<Widget, NewOrderView> {}
private static Binder uiBinder = GWT.create(Binder.class);
interface Driver extends RequestFactoryEditorDriver<OrganizationProxy, OrganizationEditor> {}
Driver driver = GWT.create(Driver.class);
#UiField
Button save;
#UiField
OrganizationEditor orgEditor;
AdminRequestFactory requestFactory;
AdminRequestFactory.OrderRequestContext requestContext;
OrganizationProxy organization;
public NewOrderView()
{
initWidget(uiBinder.createAndBindUi(this));
requestFactory = createFactory();
requestContext = requestFactory.contextOrder();
driver.initialize(requestFactory, orgEditor);
String[] paths = driver.getPaths();
createFactory().contextOrder().findOrganizationById(1).with(paths).fire(new Receiver<OrganizationProxy>()
{
#Override
public void onSuccess(OrganizationProxy response)
{
if (response == null)
{
organization = requestContext.create(OrganizationProxy.class);
organization.setContactPerson(requestContext.create(PersonProxy.class));
} else
organization = requestContext.edit(response);
driver.edit(organization, requestContext);
}
#Override
public void onFailure(ServerFailure error)
{
createConfirmationDialogBox(error.getMessage()).center();
}
});
}
private static AdminRequestFactory createFactory()
{
AdminRequestFactory factory = GWT.create(AdminRequestFactory.class);
factory.initialize(new SimpleEventBus());
return factory;
}
#UiHandler("save")
void buttonClick(ClickEvent e)
{
e.stopPropagation();
save.setEnabled(false);
try
{
AdminRequestFactory.OrderRequestContext ctx = (AdminRequestFactory.OrderRequestContext) driver.flush();
if (!driver.hasErrors())
{
// Link to each other
PersonProxy contactPerson = organization.getContactPerson();
contactPerson.setOrganization(organization);
String[] paths = driver.getPaths();
ctx.saveOrganization(organization).with(paths).fire(new Receiver<Void>()
{
#Override
public void onSuccess(Void arg0)
{
createConfirmationDialogBox("Saved!").center();
}
#Override
public void onFailure(ServerFailure error)
{
createConfirmationDialogBox(error.getMessage()).center();
}
});
}
} finally
{
save.setEnabled(true);
}
}
}
with() is only used for retrieval of information, so your with() use with a void return type is useless (but harmless).
Whether a full graph is persisted is entirely up to your server-side code, which is intimately bound to your persistence API (JPA, JDO, etc.)
First, check that the Organization object you receive in your save() method on the server-side is correctly populated. If it's not the case, check your Locators (and/or static findXxx methods) ; otherwise, check your save() method's code.
Judging from the code above, I can't see a reason why it wouldn't work.
It took me some time to realize that the problem was the composite id of Person entity.
Below is the code snippet of PojoLocator that is used by my proxy entities.
public class PojoLocator extends Locator<DatastoreObject, Long>
{
#Override
public DatastoreObject find(Class<? extends DatastoreObject> clazz, Long id)
{
}
#Override
public Long getId(DatastoreObject domainObject)
{
}
}
In order to fetch child entity from DataStore you need to have id of a parent class. In order to achieve that I switched "ID class" for Locator<> to String which represents textual form of Objectify's Key<> class.
Here is how to looks now:
public class PojoLocator extends Locator<DatastoreObject, String>
{
#Override
public DatastoreObject find(Class<? extends DatastoreObject> clazz, String id)
{
Key<DatastoreObject> key = Key.create(id);
return ofy.load(key);
}
#Override
public String getId(DatastoreObject domainObject)
{
if (domainObject.getId() != null)
{
Key<DatastoreObject> key = ofy.fact().getKey(domainObject);
return key.getString();
} else
return null;
}
}
Please note that your implementation may slightly differ because I'm using Objectify4.