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.
Related
I'm using Spring Data MongoDB and Spring Data Rest to create a REST API which allows GET, POST, PUT and DELETE operations on my MongoDB database and it's all working fine except for the update operations (PUT). It only works if I send the full object in the request body.
For example I have the following entity:
#Document
public class User {
#Id
private String id;
private String email;
private String lastName;
private String firstName;
private String password;
...
}
To update the lastName field, I have to send all of the user object, including the password ! which is obviously very wrong.
If I only send the field to update, all the others are set to null in my database. I even tried to add a #NotNull constraints on those fields and now the update won't even happens unless I send all of the user object's fields.
I tried searching for a solution here but I only found the following post but with no solution: How to update particular field in mongo db by using MongoRepository Interface?
Is there a way to implement this ?
Spring Data Rest uses Spring Data repositories to automatically retrieve and manipulate persistent data using Rest calls (check out https://docs.spring.io/spring-data/rest/docs/current/reference/html/#reference).
When using Spring Data MongoDB, you have the MongoOperations interface which is used as a repository for your Rest endpoints.
However MongoOperations currently does not supports specific fields updates !
PS: It will be awesome if they add this feature like #DynamicUpdate in Spring Data JPA
But this doesn't mean it can be done, here's the workaround I did when I had this issue.
Firstly let me explain what we're going to do:
We will create a controller which will override all the PUT operations so that we can implement our own update method.
Inside that update method, we will use MongoTemplate which do have the ability to update specific fields.
N.B. We don't want to re-do these steps for each model in our application, so we will retrieve which model to update dynamically. In order to do that we will create a utility class. [This is optional]
Let's start by adding the org.reflections api to our project dependency which allows us to get all the classes which have a specific annotation (#Document in our case):
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
</dependency>
Then create a new class, called UpdateUtility and add the following methods and also replace the MODEL_PACKAGE attribute with your own package containing your entities:
public class UpdateUtility {
private static final String MODEL_PACKAGE = "com.mycompany.myproject.models";
private static boolean initialized = false;
private static HashMap<String, Class> classContext = new HashMap<>();
private static void init() {
if(!initialized) {
Reflections reflections = new Reflections(MODEL_PACKAGE);
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Document.class); // Get all the classes annotated with #Document in the specified package
for(Class<?> model : classes) {
classContext.put(model.getSimpleName().toLowerCase(), model);
}
initialized = true;
}
}
public static Class getClassFromType(String type) throws Exception{
init();
if(classContext.containsKey(type)) {
return classContext.get(type);
}
else {
throw new Exception("Type " + type + " does not exists !");
}
}
}
Using this utility class we can retreive the model class to update from it's type.
E.g: UpdateUtility.getClassFromType() will returns User.class
Now let's create our controller:
public class UpdateController {
#Autowired
private MongoTemplate mongoTemplate;
#PutMapping("/{type}/{id}")
public Object update(#RequestBody HashMap<String, Object> fields,
#PathVariable(name = "type") String type,
#PathVariable(name = "id") String id) {
try {
Class classType = UpdatorUtility.getClassFromType(type); // Get the domain class from the type in the request
Query query = new Query(Criteria.where("id").is(id)); // Update the document with the given ID
Update update = new Update();
// Iterate over the send fields and add them to the update object
Iterator iterator = fields.entrySet().iterator();
while(iterator.hasNext()) {
HashMap.Entry entry = (HashMap.Entry) iterator.next();
String key = (String) entry.getKey();
Object value = entry.getValue();
update.set(key, value);
}
mongoTemplate.updateFirst(query, update, classType); // Do the update
return mongoTemplate.findById(id, classType); // Return the updated document
} catch (Exception e) {
// Handle your exception
}
}
}
Now we're able to update the specified fields without changing the calls.
So in your case, the call would be:
PUT http://MY-DOMAIN/user/MY-USER-ID { lastName: "My new last name" }
PS: You can improve it by adding the possibility to update specific field in a nested objects...
I have crud methods that modify the data in the cache and database
I also have a method that returns all entities when I use it after changes in the cache and database, I get irrelevant data.
As I understand it, the point is in the method of returning all entities. It uses the default key, it is different from other methods.
What do I need to do so that I can return the actual data sheet?
#Service
#CacheConfig(cacheNames = "configuration")
class ServiceConfiguration{
#Cacheable //this method returns non actual data
public List<MySomeConfiguration> getAllProxyConfigurations() {
return repository.getAllConfigurations();
}
#Cacheable(key = "#root.target.getConfigurationById(#id).serverId")
public MySomeConfiguration getConfigurationById(Long id) {
...
return configuration;
}
#CachePut(key = "#configuration.serverId", condition = "#result.id != null")
public MySomeConfiguration addOrUpdateConfiguration(Configuration configuration) {
return configuration;
}
#Cacheable(key = "#serverId")
public MySomeConfiguration getConfigurationByServerId(String serverId) {...
return configuration;
}
#CacheEvict(key = "#root.target.getConfigurationById(#id).serverId")
public void deleteConfigurationById(Long id) {
...
}
}//end class
p.s. sorry for my english
By default redis cache manager uses StringRedisSerializer for Key serializer
Your class toString() is used to serializer the key of the object so don't give different keys for different methods like put, get, evict etc jus rely on your toString() to get the key or override by using Spring Cache Custom KeyGenerator
Refer
https://www.baeldung.com/spring-cache-custom-keygenerator
I need to find a proper solution to have a Spring-Boot #Component (singleton) class hold a List of database table objects, which could be accessed throughout the life of an application. I need to get a value of a certain language column value (there could be many language columns) depending on the parameters.
My idea was to do it like this:
#Component
public class CardTypeValueComponent {
private List<CardTypesTabModel> listOfCardTypes;
private CardTypesModelRepository cardTypesModelRepository;
private static final String UNKNOWN = "-";
#Autowired
public CardTypeValueComponent(CardTypesModelRepository cardTypesModelRepository) {
Assert.notNull(cardTypesModelRepository, "CardTypesModelRepository cannot be null");
this.cardTypesModelRepository = cardTypesModelRepository;
}
#PostConstruct
private void getAllCardTypesFromDb() {
this.listOfCardTypes = cardTypesModelRepository.findAll();
}
public String getCardTypeLanguageValue(int cardType, String language) {
String cardTypeLangValue = UNKNOWN;
for (CardTypesTabModel cardTypesTabModel : listOfCardTypes) {
if (cardTypesTabModel.getTypeId() == cardType && "spanish".equals(language)) {
cardTypeLangValue = cardTypesTabModel.getSpanishValue();
} else {
cardTypeLangValue = cardTypesTabModel.getEnglishValue();
}
}
return cardTypeLangValue;
}
}
Is it a proper way of completing such a task whilst keeping in mind that the table object column count could increase in the future?
Excuse me for the pseudo code. Thanks.
Added more details:
CardTypesTabModel Entity class:
#Entity
public class CardTypesTabModel {
private int type;
private String englishValue;
private String spanishValue;
// other values, getters & setters
}
What you're trying to do is re-inventing the caching mechanisme.
You may consider to relay on the Spring Cache Abstraction http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html then choose JCache (JSR-107) as implementation.
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.
In my datamodel a have many entities where attributes are mapped to enumerations like this:
#Enumerated(EnumType.STRING)
private MySpecialEnum enumValue;
MySpecialEnum defines some fixed values. The mapping works fine and if the database holds a NULL-value for a column I get NULL in the enumValue-attribute too.
The problem is, that my backend module (where I have no influence on) uses spaces in CHAR-columns to identify that no value is set. So I get an IllegalArgumentException instead of a NULL-value.
So my question is: Is there a JPA-Event where I can change the value read from the database before mapping to the enum-attribute?
For the write-access there is the #PrePersist where I can change Null-values to spaces. I know there is the #PostLoad-event, but this is handled after mapping.
Btw: I am using OpenJpa shipped within WebSphere Application Server.
You could map the enum-type field as #Transient (it will not be persisted) and map another field directly as String, synchronizing them in #PostLoad:
#Transient
private MyEnum fieldProxy;
private String fieldDB;
#PostLoad
public void postLoad() {
if (" ".equals(fieldDB))
fieldProxy = null;
else
fieldProxy = MyEnum.valueOf(fieldDB);
}
Use get/setFieldProxy() in your Java code.
As for synchronizing the other way, I'd do it in a setter, not in a #PreUpdate, as changes to #Transient fields probably do not mark the entity as modified and the update operation might not be triggered (I'm not sure of this):
public void setFieldProxy(MyEnum value) {
fieldProxy = value;
if (fieldProxy == null)
fieldDB = " ";
else
fieldDB = value.name();
}
OpenJPA offers #Externalizer and #Factory to handle "special" database values.
See this: http://ci.apache.org/projects/openjpa/2.0.x/manual/manual.html#ref_guide_pc_extern_values
You might end up with something like this: not tested...
#Factory("MyClass.mySpecialEnumFactory")
private MySpecialEnum special;
...
public static MySpecialEnum mySpecialEnumFactory(String external) {
if(StringUtils.isBlank(external) return null; // or why not MySpecialEnum.NONE;
return MySpecialEnum.valueOf(external);
}