Tapestry5 Value Encoder with Hibernate Composite Key - java

I'm trying to get my tapestry value encoder to work with a hibernate composite key. I have the following code and I'm trying to get the composite id and pass it to the interface where it could later be sent back to the server for decoding back to an object.
#Embeddable
public class IfasvVendorPK implements Serializable{
#Column(length = 4, nullable = false)
protected String peId;
#Column(length = 8, nullable = false)
protected String peAddrCd;
public IfasvVendorPK() {
}
public IfasvVendorPK(String peId, String peAddrCd) {
this.peId = peId;
this.peAddrCd = peAddrCd;
}
// equals, hashCode
}
#Entity
public class IfasvVendor implements Serializable {
#EmbeddedId
private IfasvVendorPK ifasvVendorPK;
//...
}
The following is my value encoder. The toClient is where would I need to send the composite key to the interface. I'm not sure how to get the composite key.
#SuppressWarnings("unchecked")
public LabelAwareValueEncoder getEncoderVendor() {
return new LabelAwareValueEncoder<IfasvVendor>() {
public String toClient(IfasvVendor value) {
return value.getIfasvVendorPK().toString();
}
public IfasvVendor toValue(String clientValue) {
if (clientValue.isEmpty()) {
return null;
}
return (IfasvVendor) session.get(IfasvVendor.class, clientValue);
}
public String getLabel(IfasvVendor value) {
return value.getPeNameU();
}
};
}
If someone could help me to better understand how to work with the composite key so I could get my value encoder working, it would be greatly appreciated. Thanks in advance.

Hibernate has no way to know how what this string means and cannot convert it back.
I suggest adding a non-composite ID or concaternate the values which you then split again in your toValue method.
If you keep the ValueEncoder longer than your request (ex. with #Persist) you could put a HashMap in it to easily get the object back for a concaternated client key;
Since your Composite key is serializeable you could serialize it in toClient and deserialize it in toValue. However, I really wouldn't do that, serializing stuff and sending it to a browser and back is a big, evil security hole.

I think the problem is with your line:
return (IfasvVendor) session.get(IfasvVendor.class, clientValue);
At this point, your "clientValue" is a String, as generated by IfasvVendorPK.toString()
I'm not sure this should work in hibernate, shouldn't you be passing an instance of IfasvVendorPK to session.get?
You can test whether this should work with the following:
public String toClient(IfasvVendor value) {
// test toValue strategy (probably breaks):
System.out.println(
session.get(IfasvVendor.class, value.getIfasvVendorPK().toString()));
// test toValue strategy (probably works):
System.out.println(
session.get(IfasvVendor.class, value.getIfasvVendorPK()));
return value.getIfasvVendorPK().toString();
}
So if I'm right, what you need to do in toValue is convert the String back to an IfasvVendorPK before sending it to session.get.

Related

Not able to convert underscore case to camel case with Jackson

I have a DTO class which has a property like:
#JsonIgnoreProperties(ignoreUnknown = true)
public class WPPostResponse {
#JsonProperty("featuredMedia")
Long featured_media;
public Long getFeatured_media() {
return featured_media;
}
public void setFeatured_media(Long featured_media) {
this.featured_media = featured_media;
}
}
The input JSON has the key featured_media. I convert the JSON string to the object and then sends it to the client response as JSON. I want the final response JSON to have featuredMedia as the key. I am however getting null as the value. If I remove the JsonProperty, it gives the value, but the key is having underscore. How to fix this? Thanks.
Always respect the Java naming conventions in your Java code. Use annotations to deal with Json not respecting them.
In this case, use JsonAlias
Annotation that can be used to define one or more alternative names for a property, accepted during deserialization as alternative to the official name
public class WPPostResponse {
#JsonAlias("featured_media")
Long featuredMedia;
public Long getFeaturedMedia() {
return featuredMedia;
}
public void setFeaturedMedia(Long featuredMedia) {
this.featuredMedia = featuredMedia;
}
}
You can use the JsonProperty on setters and getters to have different namings during serialization and deserialization
#JsonIgnoreProperties(ignoreUnknown = true)
public class WPPostResponse {
Long featuredMedia;
#JsonProperty("featuredMedia") // output will be featuredMedia
public Long getFeatured_media() {
return featuredMedia;
}
#JsonProperty("featured_media") // input should be featured_media
public void setFeatured_media(Long featured_media) {
this.featuredMedia = featured_media;
}
}
And also you set access level to #JsonProperty annotation
#JsonProperty(value = "featured_media", access = JsonProperty.Access.WRITE_ONLY)

String increment for alphanumeric field is for JPA not working

https://vladmihalcea.com/how-to-implement-a-custom-string-based-sequence-identifier-generator-with-hibernate/
i tried to this for a field that is not primary key.
Also same solution for here:
How to implement IdentifierGenerator with PREFIX and separate Sequence for each entity
But even it does not go to Java method when i run the program. It saves as null.
And i cant see the log that i put inside my class. There is no log for my class.
I copied from that blog but my code is:
public class StringSequenceIdentifier
implements IdentifierGenerator, Configurable {
public static final String SEQUENCE_PREFIX = "sequence_prefix";
private String sequencePrefix;
private String sequenceCallSyntax;
#Override
public void configure(
Type type, Properties params, ServiceRegistry serviceRegistry)
throws MappingException {
System.out.println("xxx");
final JdbcEnvironment jdbcEnvironment =
serviceRegistry.getService(JdbcEnvironment.class);
final Dialect dialect = jdbcEnvironment.getDialect();
final ConfigurationService configurationService =
serviceRegistry.getService(ConfigurationService.class);
String globalEntityIdentifierPrefix =
configurationService.getSetting( "entity.identifier.prefix", String.class, "SEQ_" );
sequencePrefix = ConfigurationHelper.getString(
SEQUENCE_PREFIX,
params,
globalEntityIdentifierPrefix);
final String sequencePerEntitySuffix = ConfigurationHelper.getString(
SequenceStyleGenerator.CONFIG_SEQUENCE_PER_ENTITY_SUFFIX,
params,
SequenceStyleGenerator.DEF_SEQUENCE_SUFFIX);
final String defaultSequenceName = ConfigurationHelper.getBoolean(
SequenceStyleGenerator.CONFIG_PREFER_SEQUENCE_PER_ENTITY,
params,
false)
? params.getProperty(JPA_ENTITY_NAME) + sequencePerEntitySuffix
: SequenceStyleGenerator.DEF_SEQUENCE_NAME;
sequenceCallSyntax = dialect.getSequenceNextValString(
ConfigurationHelper.getString(
SequenceStyleGenerator.SEQUENCE_PARAM,
params,
defaultSequenceName));
}
#Override
public Serializable generate(SharedSessionContractImplementor session, Object obj) {
System.out.println("xxx");
if (obj instanceof Identifiable) {
Identifiable identifiable = (Identifiable) obj;
Serializable id = identifiable.getId();
if (id != null) {
return id;
}
}
long seqValue = ((Number) Session.class.cast(session)
.createSQLQuery(sequenceCallSyntax)
.uniqueResult()).longValue();
return sequencePrefix + String.format("%011d%s", 0 ,seqValue);
}
}
That is in my domain:
#GenericGenerator(
name = "assigned-sequence",
strategy = "xxxxxx.StringSequenceIdentifier",
parameters = {
#org.hibernate.annotations.Parameter(
name = "sequence_name", value = "hibernate_sequence"),
#org.hibernate.annotations.Parameter(
name = "sequence_prefix", value = "CTC_"),
}
)
#GeneratedValue(generator = "assigned-sequence", strategy = GenerationType.SEQUENCE)
private String referenceCode;
WHAT I WANT IS
I need a unique field, which is not primary. So, i decided that incrementing is best solution because otherwise, i have to check for each created random if it exists in database (i also open suggestions for this).
It will be around 5-6 characters and alphanumeric.
I want to make JPA increment this but it seems i cant do it.
This is very similar to Hibernate JPA Sequence (non-Id) but I don't think it's an exact duplicate. Yet the answers seem to apply and they seem to suggest the following strategies:
Make the field to be generated a reference to an entity with the only purpose that the field now becomes an ID and can get generated by the usual strategies. https://stackoverflow.com/a/536102/66686
Use #PrePersist to fill the field before it gets persisted. https://stackoverflow.com/a/35888326/66686
Make it #Generated and generate the value in the database using a trigger or similar. https://stackoverflow.com/a/283603/66686

Dealing with changed ENUM definitions - database

Introduction
The lead architect went and changed the ENUM definition in a spring boot project.
From:
public enum ProcessState{
C("COMPLETE"), P("PARTIAL");
}
To:
public enum ProcessState{
COMPLETE("COMPLETE"), PARTIAL("PARTIAL");
}
What is the proper way to deal with this? Some other Java Spring Boot applications are now breaking. Would there be a way to tell the jackson deserializer to perform some kind of conversion in these situations?
My Current Work-Around
What I did was to run two update statements on the oracle database:
UPDATE store set PAYLOAD = REPLACE(PAYLOAD, '"processState":"P"','"processState":"PARTIAL"') where PAYLOAD like '%"processState":"P"%';
UPDATE store set PAYLOAD = REPLACE(PAYLOAD, '"processState":"C"','"processState":"COMPLETE"') where PAYLOAD like '%"processState":"C"%';
Question
So are there other ways? Could I do it by adding some deserialization/conversion code somewhere for these specific cases? Is there a more elegant way than running a replace SQL statement?
Could I do some kind of hack on a specific java sub-package, and say "use this enum instead of that enum..." or use one of the two? But without affecting the rest of the code?
The error:
java.lang.IllegalArgumentException: No enum constant
Ideally we store value of emum rather than Enum.
So, you should save ENUM values like COMPLETE,PARTIAL
For JSON serialization and de-serialization, use #JsonValue
#JsonValue
public String toValue() {
return value;
}
One additional solution to the others posted:
#JsonCreator
public static ProcessState factory(String inputValue) {
if(inputValue.length() == 1){
for(ProcessState type : ProcessState.values()){
if(inputValue.equals(type.getValue().substring(0,inputValue.length()))){
return type;
}
}
}
return ProcessState .valueOf(inputValue);
}
Implement a JPA converter like this:
#Converter(autoApply = true)
public class ProcessStateConverter
implements AttributeConverter<ProcessState, String> {
private ImmutableBiMap<ProcessState, String> map = ImmutableBiMap.<ProcessState, String>builder()
.put(COMPLETE, "C")
.put(COMPRESSING, "P")
.build();
#Override
public String convertToDatabaseColumn(ProcessState attribute) {
return Optional.ofNullable(map.get(attribute))
.orElseThrow(() -> new RuntimeException("Unknown ProcessState: " + attribute));
}
#Override
public ProcessState convertToEntityAttribute(String dbData) {
return Optional.ofNullable(map.inverse().get(dbData))
.orElseThrow(() -> new RuntimeException("Unknown String: " + dbData));
}
}
Remember to treat your Enum like a simple column and not #Enumerated i.e.
#Entity
public class MyEntity {
#Column //no #Enumerated
private ProcessState processState;
//...
}
The drawback is that you need to maintain the converter each time something changes. So better create a unit test to check if everything is correctly mapped.

How to encrypt/decypt data with custom anotation(hibernate) in spring project

I'm developing some RESTFull web services for a project. I use the Spring framework and use gradle for build.
The problem is , I want to encrypt and decrypt data table when write and read data. I already have a algorithm(class) to encrypt and decrypt data with AES etc. What I need is, how annotate this method to hibernate entity class, am I need to create bean for this class ?
Ex:-
#Column(columnDefinition= "LONGBLOB", name = "card_no")
#ColumnTransformer(
read="decrypt(card_no)",
write="encrypt(?)")
private String cardNo;
Like this I want to add my own encryption/decryption java method to here.
If you have access to JPA 2.1, I would advocate for the use of an #Convert annotation with an AttributeConverter implementation.
An AttributeConverter defines a contract between the state of an entity property when it is serialized to the datastore and when it is deserialized from the datastore.
public class CreditCard {
#Convert(converter = CreditCardNumberConverter.class)
private String creditCardNumber;
}
Your converter implementation might look like this
public class CreditCardNumberConverter implements AttributeConverter<String, String> {
#Override
public String convertToDatabaseColumn(String attribute) {
/* perform encryption here */
}
#Override
public String convertToEntityAttribute(String dbData) {
/* perform decryption here */
}
}
If you are not able to leverage JPA 2.1, an EntityListener or the use of #PrePersist, #PreUpdate, and #PostLoad may be used in order to perform similar logic for encrypting and decrypting the database value.
Just be sure that if you decide to use an EntityListener or any of the Pre/Post callback method annotations, store the decrypted result in a transient field and use that field as your business layer's usage, such as follows:
public class CreditCard {
// this field could have package private get/set methods
#Column(name = "card_number", length = 25, nullable = false)
private String encrpytedCardNumber;
// this is the public operated upon field
#Transient
private String cardNumber;
#PostLoad
public void decryptCardNumber() {
// decrypts card number during DATABASE READ
this.cardNumber = EncryptionUtils.decrypt(encryptedCardNumber);
}
#PrePersist
#PreUpdate
public void encryptCardNumber() {
// encrypts card number during INSERT/UPDATE
this.encryptedCardNumber = EncryptionUtils.encrypt(cardNumber);
}
}
Doing the above keeps entity state consistent in the object as to what exists in your database, without having Hibernate believing that the entity has changed immediately upon loading the database data.
You could do this in several ways.
Using JPA Listeners
Following is a simple example. Please change accordingly.
public class CustomListener{
#Inject
private EncryptorBean encryptor;
#PostLoad
#PostUpdate
public void decrypt(Object pc) {
if (!(pc instanceof)) {
return;
}
MyObj obj = (MyObj) pc;
if (obj.getCardNo() != null) {
obj.setCardNo(
encryptor.decryptString(user.getEncryptedCardNo);
}
}
#PrePersist
#PreUpdate
public void encrypt(Object pc) {
if (!(pc instanceof MyObj)) {
return;
}
MyObj obj = (MyObj ) pc;
if (obj.getCardNo() != null) {
user.setEncryptedCardNo(
encryptor.encryptString(user.getCardNo());
}
}
}
With this approach, you might have to take some precaution to avoid encrypting a already encrypted cardNo value. An additional Transient property could be used to hold the state whether the cardNo is already encrypted or not.
Or Simply Implementing this feature in the getters and setters of the entity property.
public String getCardNo(){
return EncrypUtil.decrypt(this.cardNo);
}
public void setCardNo(String cardNo){
this.cardNo = EncrypUtil.encrypt(cardNo);
}
You could also use JPA vendor specific interceptors. i.e HibernateInterceptors
public class CustomInterceptor extends EmptyInterceptor{
public boolean onSave(Object entity,Serializable id,
Object[] state,String[] propertyNames,Type[] types)
throws CallbackException {
if (entity instanceof MyObj){
// check if already encrypted or not.
//(A transient property could be useful)
entity.setCardNo(EncrypUtils.encrypt(entity.getCardNo()));
}
You could also use #Convert annotation and specify a converter
#Convert(converter = CCConverter.class)
private String creditCardNumber;
CCConverter class should be an implementation of AttributeConverter
Hope this helps.

Hibernate Enum mapping using annotaions

I have an existing database that I am now connecting to using hibernate. I cannot change the data in it at the moment and have everything working apart from a single column.
I have a status column that has the values:
new
mailed
in
out
And the column is mapped as follows:
#Column(name = "STATUS", nullable = false, length = 50)
#Enumerated(EnumType.STRING)
private TeamMemberStatus status;
I would REALLY like (for application reasons) to have this column mapped as a Java Enum (TeamMemberStatus), but due to the fact that 'new' is a keyword in Java I cannot have that as an enum member.
If I have the enum contstants NEW, MAILED, IN and OUT hibernate fails as inside EnumType it does a Enum.valueOf().
Is there any way for me to map this to my Enum without having to write a complex UserType?
-- added content
My Enum like this:
public enum TeamMemberStatus {
NEW, MAILED, IN, OUT
}
is a valid Java enum, but not matching the case of the database. If I change it to match the database like:
public enum TeamMemberStatus {
new, mailed, in, out
}
It won't compile as 'new' is a Java reserved word.
If you can use a SQL UPPER statement at database, It will work without using any UserType
UPDATE
Well, It can not be The nicest solution but it solves what you want
#Entity
public class WrapperEntity {
private TeamMemberStatus memberStatus;
#Transient
private TeamMemberStatus getMemberStatus() {
return this.memberStatus;
}
public void setMemberStatus(TeamMemberStatus memberStatus) {
this.memberStatus = memberStatus;
}
#Column(name="STATUS", nullable=false, length=50)
public String getMemberStatusAsString() {
return memberStatus.name().toLowerCase();
}
public void setMemberStatusAsString(String memberStatus) {
this.setsetMemberStatus(TeamMemberStatus.valueOf(memberStatus.toUpperCase()));
}
}
If your Database values are "new", "mailed", "in" and "out" then your Enum need exactly the same names. - I believe that the problem is, that your Enums are in capital letters but your data base values not.

Categories