Storing a reference to a java class in a database - java

I have some kind of job control with persistence via a database.
There is a interface for actions:
public interface IAction {
Object perform(Work work, Map<String, String> parameter) throws Exception;
}
There are multiple implementations:
public class SingleFileConvertAction implements IAction {
public InputStream perform(Work work, Map<String, String> parameter) throws Exception {
// ...
}
}
public class CacheDeleteAction implements IAction {
public Object perform(Work work, Map<String, String> parameter) throws Exception {
// ...
}
}
// ...
There is a job control class:
#Entity
public class ActionControl {
#Id
#GeneratedValue
private Integer id;
#ElementCollection
private Map<String, String> parameter = new HashMap<String, String>();
#ManyToOne
#JoinColumn(name = "work_id", referencedColumnName = "id", nullable = false)
private Work work;
private IAction action;
private Date requestTime;
private Date startTime;
private Date endTime;
// ...
private ActionControl() {}
public ActionControl(Work work, String action, Map<String, String> parameter) {
this.parameter = parameter;
this.work = work;
// ...
}
}
Now I want to save the action control to the database. I only need to know, which action class to use. Everything else is saved in work and parameter.
I thought about saving a string and do a switch() to choose it like "CacheDeleteAction" -> CacheDeleteAction but I assume there is a better way to do it. Is it possible to save "CacheDeleteAction.class" in a database field? (I saw it in Spring annotations)
How to save a reference to a java class in a database?

as #XtremeBaumer said depends on your usage, if you need to create an instance later on then what XtremeBaumer has suggested would be preferred way, else with enum it would go something like this,
public enum IActionEnum {
CACHE_DELETE_ACTION("Cache delete action"),
SINGLE_FILE_CONVERT_ACTION("Single file convert action"),
private String actionDescription;
IActionEnum(String description) {
actionDescription= description;
}
#Override
public String toString() {
return actionDescription;
}
}
And whenever you would like to save it to DB you can use it like,
IAction actionControl = new IAction();
// set all other parameters
actionControl.setIAction(IActionEnum.CACHE_DELETE_ACTION)
actionControlRepository.save(actionControl);

Related

Is it possible to map a JPA/Hibernate Entity field to a field in another class without #Embeddable?

I have a very simple Entity (Person.java) that I am wanting to persist via JPA/Hibernate.
The Entity contains two fields: ID and Identification String.
The ID is a simple Integer, and is no problem. The Identification String is currently a String, but for various reasons, I want to instead use a wrapper class for String (IDString), where there are various validation methods among other things.
I am wondering how I can get JPA/Hibernate to use the wrapped string (inside the custom class IDString) when persisting the Person table in the database. I know this can probably be solved by letting the IDString be #Embeddable and then embed IDString in the Person entity with #Embedded, but I am looking for another method, mostly because IDString is in an entirely different package, and I am reluctant to have to go there and change stuff.
Googling, I found https://www.baeldung.com/hibernate-custom-types, but it seems to be mostly about more complicated cases, where you want to convert one class into another type, and I do feel that there is probably a smarter way that I am simply overlooking.
Here is the entity (in theory)
#Entity(name="Person")
#Table(name="DB_TABLE_PERSON")
public class Person implements Serializable {
#Id
Integer id;
// WHAT SHOULD I PUT HERE? I WANT TO SIMPLY USE THE STRING INSIDE IDSTRING AS THE FIELD TO PERSIST
IDString idString;
// getter and setter for ID.
public void getIdString() {
return idString.getValue();
}
public void setIdString(String in) {
idString.setValue(in);
}
}
And here is the class IDString (in theory):
public class IDString {
// I really want to be a POJO
private final String the_string;
public IdString(String input) {
if (isValid(input)) {
the_string = input;
} else {
throw new SomeCoolException("Invalid format of the ID String");
}
public boolean isValid(String input) {
// bunch of code to validate the input string
}
public String getValue() {
return the_string;
}
public void setValue(String input) {
if (isValid(input)) the_string = s;
else throw new SomeCoolException("Invalid format of the ID String");
}
I know that I could place the validation if the IDString inside the Entity, but the IDString will be used elsewhere (it's a general custom class), so I don't want to do that. Is there a simple way?
#Converter(autoApply=true) // autoApply is reasonable, if not use #Converter on field
public class IDStringConverter implements AttributeConverter<IDString,String> {
#Override
public String convertToDatabaseColumn(IDString attribute) {
return attribute != null ? attribute.getValue() : null;
}
#Override
public IDString convertToEntityAttribute(String dbData) {
return dbData != null ? new IDString(dbData) : null;
}
}
With this you should not need any other modifications in your code. One limitation of the AttributeConverter is that it maps from exactly 1 Java field to exactly 1 DB column. If you wanted to map to more columns (not the case here), you would need embeddables.
You could also put a #Column annotation on the getter:
#Entity
public class Person {
private final IdString idString = new IdString();
#Column(name = "ID_STRiNG")
public IdString getIdString() {
return idString.getValue();
}
public void setIdString(String input) {
idString.setValue(input);
}
Another solution could be to convert to/from IdString using #PostLoad and #PrePersit event handlers:
#Entity
public class Person {
#Column(name = "ID_STRiNG")
private String the_string; // no getters & setters
#Transient
private final IdString idString = new IdString();
#PostLoad
public void postLoad() {
idString.setValue(the_string);
}
#PrePersist
public void prePersist() {
the_string = idString.getValue();
}
// getters & setters for idString

How to tell map function of the MapperModel that specific filed should have specific value?

I use ModelMapper in my project to map between DTO classes and models.
For example:
public class UserDto {
private String name;
private String phone;
private String email;
}
public class User {
#Id
private String id;
private String metaDatal;
private String name;
private String phone;
private String email;
}
Here How I map it:
#Autowired
private ModelMapper modelMapper;
modelMapper.map(userDto, user);
As you can see I have metaDatal field in the user model, I want to set this field with a specific value.
Specific field(metaDatal) of the mapped class I want to set this value "abc123".
Is there any way to tell map method when it called that, specific filed(for example metaData) should have specific value(for example abc123)?
I believe the most flexible way to do this is to implement a simple Converter. Check this:
Converter<UserDto, User> metaData = new Converter<UserDto, User>() {
// This is needed to convert as usual but not having not registered
// this converter to avoid recursion
private final ModelMapper mm = new ModelMapper();
#Override
public User convert(MappingContext<UserDto, User> context) {
User u = context.getDestination();
mm.map(context.getSource(), u);
u.setMetaDatal("abc123");
return context.getDestination();
}
};
Now it is just to create a TypeMap and set this converter to handle conversion, like:
modelMapper.createTypeMap(UserDto.class, User.class).setConverter(metaData);
before modelMapper.map().
You could also add a getter for metadata in your UserDto, like:
public String getMetaDatal() {
return "abc123";
}
If it is something that can be derived from UserDto directly and skip the converter part.

How to import a csv with OpenCSV to FX SimpleObjectProperties?

For a school assignment, I need to parse a CSV into a Bean and present it in a JavaFX GUI later. I decided to use the Library opencsv, which worked fine.
But now, I would like to parse the attributes directly into SimpleObjectProperties. How do I do that? Unfortunately, I couldn't find any further information.
Code looks like this:
public class Phone {
#CsvBindByName(column = "ENTITY_ID")
private SimpleIntegerProperty entityId;
#CsvBindByName(column = "OPERATING_COMPANY")
private SimpleStringProperty operatingCompany;
When I run the code, I get a CsvDataTypeMismatchException (Conversion of 1006 to javafx.beans.property.SimpleIntegerProperty failed).
Any help much appreciated, thank you!!
Looking at the documentation it looks like you can create CustomConverts for each type of property you have; the example they have on the documentation page, this is the start to an IntegerPropertyConverter.
public class IntegerPropertyConverter extends AbstractCsvConverter {
#Override
public Object convert(String value) {
return new SimpleIntegerProperty(Integer.parseInt(value));
}
#Override
public String convertToWrite(Object value) {
IntegerProprety prop = (IntegerProperty) value;
return String.format("%d", prop.get());
}
}
Then you'd use:
#CsvCustomBindByName(column = "ENTITY_ID", converter = IntegerPropertyConverter.class)
private SimpleIntegerProperty entityId;
If you need to create your properties using the longer format, you will need to override other methods in the AbstractBeanField, such as public final void setFieldValue(T bean, String value, String header) where you can actually use the bean to create the
There is no easy way around this.
You keep your Phone a POJO and map the entire object as a property
private SimpleObjectProperty<Phone> phone = new SimpleObjectProperty<Phone>();
Or you could add properties to the Phone
public class Phone {
#CsvBindByName(column = "ENTITY_ID")
private Integer entityId;
private final SimpleIntegerProperty entityIdProperty;
public Phone() {
entityIdProperty = new SimpleIntegerProperty();
entityIdProperty.addListener((o, oldValue,newValue)->{
entityId = newValue.intValue();
});
}
public Integer getEntityId() {
return entityId;
}
public void setEntityId(Integer entityId) {
this.entityId = entityId;
entityIdProperty.set(entityId);
}
public SimpleIntegerProperty getEntityIdProperty() {
return entityIdProperty;
}
// ...
}
If you do not need this object to have a bidirectional binding you could skip the listener.
There are other possibilities too, like having Methods and constructors to convert from a Phone to a PhoneFX (with properties instead of simple types) and viceversa.

Eclipselink: Adding Where-Clause using customizer

In my current project, we will refractor the nls-support for database entities. In our previous version, we used the session language, but unfortualy this behaviour was not completely stable. So we will change the code, so that the language information is stored inside the query.
I would love to have a central instance to handle this language behaviour instead of changing each query, in every entity spread over the whole project.
E.g. I have this entity:
#NamedQueries({
#NamedQuery(name = NLSBackendEntity.findAll,
query = "select n from NLSBackendEntity n"),
#NamedQuery(name = NLSBackendEntity.getById,
query = "select n from NLSBackendEntity n where n.nlsBackendKey.key = :key") })
#Entity
#Table(name = "backend_key_al")
public class NLSBackendEntity implements Serializable
{
private static final long serialVersionUID = 1L;
public static final String findAll = "NLSBackend.findAll";
public static final String getById = "NLSBackend.getById";
#EmbeddedId
private NLSBackendKey nlsBackendKey;
/**
* The text in the language.
*/
#Lob
#Column(name = "TEXT")
private String text;
NLSBackendEntity()
{
// no arg constructor needed for JPA
}
public String getKey()
{
return nlsBackendKey.key;
}
public String getLanguage()
{
return nlsBackendKey.language;
}
public String getText()
{
return text;
}
#Embeddable
public static class NLSBackendKey implements Serializable
{
private static final long serialVersionUID = 1L;
/**
* the NLS-key.
*/
#Column(name = "KEY")
private String key;
/**
* The language of this entry.
*/
#Column(name = "LOCALE")
private String language;
}
}
One possibility would now be to add the n.nlsBackenKey.language = :locale to every NamedQuery and to change every call, where this NamedQuery is referenced.
A more favorable way would be, to have a Customizer to add the locale paramter. Atm I have this:
public class QueryLanguageCustomizer implements DescriptorCustomizer
{
#Override
public void customize(ClassDescriptor descriptor) throws Exception
{
ExpressionBuilder eb = new ExpressionBuilder(descriptor.getJavaClass());
Expression languageExp = eb.getField("LOCALE").equal(eb.getParameter("locale"));
descriptor.getQueryManager().setAdditionalJoinExpression(languageExp);
}
}
And I added this to the NLSBackendEntity: #Customizer(QueryLanguageCustomizer.class)
But now, I'm not able to set this parameter. Again, my favored way, would be to use a SessionEventAdapter:
public class LanguageSessionEventListener extends SessionEventAdapter {
/** Log for logging. */
private static final Log LOG = LogFactory
.getLog(LanguageSessionEventListener.class);
#Override
public void preExecuteQuery(SessionEvent event) {
LOG.debug("preExecuteQuery called for session= "
+ event.getSession().getName());
event.getQuery().getTranslationRow().put("LOCALE", getLocale());
super.preExecuteQuery(event);
}
private String getLocale() {
// uninteresting for this example
}
}
Unfortunatly no matter what I try, I'm unable to set this parameter. The getTransaltionRow() returns null, and every other possibility I tried failed.
Are there no possibilitys to set this parameter inside the preExecuteQuery block?
I'm using Eclipselink 2.5, any help is highly appreciated
If you don't mind using vendor-specific solution you could use EclipseLink #AdditionalCriteria annotation. You could use it as follows:
Create an abstract mapped class and derive all entities from it:
#MappedSuperclass
#AdditionalCriteria("this.language = :lang")
public class AbstractEntity {
private String language;
// getters + setters
}
Let your entities subclass it:
public class NLSBackendEntity extends AbstractEntity implements Serializable {
// ...
}
Set the value of language property on either the entity manager or entity manager factory:
entityManager.setProperty("language", "de");
before the queries are executed. EclipseLink should append language = ? to the where condition of your query binding the value you set on the entity manager.
You can use ThreadLocal<String> threadProps = new ThreadLocal<String>(); which you can set it on data or rest factory class and use it in your code.
The above solution will work if you not creating any new additional thread inside your functions.
Instead of parameter you can use threadProps.get();

Serialize/deserialize immutable objects that carry extra informations with Jackson

I try to use only immutables objects in my application. I've got a REST service that will take arbitrary JSon objects as input.
I've a Java class that map theses objects, and I want to make them immutable + able to deal with extra parameters (just like using #JsonAnySetter).
Here is my java class:
public class Operation {
private final String _id;
private final String state;
private final Map<String, Object> extra;
public Operation(String _id, String state, Map<String,Object> extra) {
this._id = _id;
this.state = state;
this.extra = extra;
}
// getters....
}
Using #JsonAnySetter I would have:
public class Operation {
private final String _id;
private final String state;
private Map<String, Object> extra = new HashMap<>();
public Operation(String _id, String state) {
this._id = _id;
this.state = state;
}
#JsonAnySetter
public void addExtra(String key, Object value) {
this.extra.put(key,value);
}
// getters....
}
But this is not immutable anymore !
This will not work because Jackson do not find any "extra" json attribute to read. I would like that everything that cannot be mapped be added to my map.
Any idea of how to do this ? (or is it just possible :)
Note: I use javac with -parameters option and the ParameterNameModule from jackson so that I don't need #JsonCreator option.
Ok so I respond to myself :)
It seems that it is not possible to do that using only Jackson.
Because I want immutability, I've turned myself to the 'immutables' framework: http://immutables.github.io/
With a little configuration, it will deal with extra parameters as stated in the following report: https://github.com/immutables/immutables/issues/185.
In my situation, I've got the following code:
#Value.Immutable
#JsonSerialize(as = ImmutableOperation.class)
#JsonDeserialize(as = ImmutableOperation.class)
public abstract class Operation {
#JsonAnyGetter
#Value.Parameter
public abstract Map<String, String> extra();
}
Refer to the documentation of immutables for the details.
If you want to deserialize immutable entity with extra arguments you can utilize builder pattern:
#JsonPOJOBuilder
public class OperationBuilder {
private String _id;
private String _state;
private Map<String, Object> extra = new HashMap<>();
#JsonAnySetter
public OperationBuilder addExtra(String key, Object value) {
this.extra.put(key,value);
return this;
}
// setters....
public Operation build() {
return new Operation(...arguments...)
}
And your original class should have this annotation on a class level:
#JsonDeserializer(builder = OperationBuilder.class)
This way all your known and unknown (extra) fields will be populated inside the builder and then Jackson will call build() method at the end of the deserialization.

Categories