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.
Related
Currently, some of our query-related APIs and Controllers are:
[GET] api/study-group?sortby=latest
[GET] api/study-group?sortby=star
[GET] api/study-group?sortby=level
#RestController
#RequiredArgsConstructor
#RequestMapping("/api/study-group")
public class StudyGroupController {
private final StudyGroupService studyGroupService;
#GetMapping
public ResponseEntity<List<StudyGroupResponseDTO>> findStudyGroup(
#RequestParam(name = "sortby", defaultValue = "createdat") String sortBy) {
studyGroupService.findAll(sortBy);
......
}
}
It handles the logic for the controller, but I wonder what kind of method would be better for branching processing for the sorting method.
The first method is branch processing through the if statement.
#Service
#RequiredArgsConstructor
public class StudyGroupService {
public List<StudyGroupResponseDTO> findAll(String sortBy) {
if(sortBy.equals("star") {
searchStudyOrderByStar()l
}else if(sortBy.equals("...")) {
....
}else {
...
}
}
private List<StudyGroupResponseDTO> sort(Function<StudyGroup, Comparable> function) {
return studyGroupRepository
.findAll()
.stream()
.sorted(Comparator.comparing(function, Comparator.reverseOrder())
.thenComparing(StudyGroup::getSeason, Comparator.reverseOrder()))
.map(studyGroupMapper::toResponseDTO)
.collect(Collectors.toList());
}
private List<StudyGroupResponseDTO> searchStudyOrderByCreatedAt() {
return sort(studyJournalService::searchLatestJournalCreatedAt);
}
private List<StudyGroupResponseDTO> searchStudyOrderByStar()
return sort(studyGroup -> studyGroup.getJournals().size());
}
private List<StudyGroupResponseDTO> searchStudyOrderByLike() {
return sort(StudyGroup::getLike);
}
}
Of course, I'm also thinking of using Enum instead of String. However, even if it is changed, setting a branch with an if statement does not seem to change.
Another way to think about it is to use Map.
#Service
#RequiredArgsConstructor
public class StudyGroupService {
private final Map<String, Function<StudyGroup, Comparable> sortMap;
public List<StudyGroupResponseDTO> findAll(String sortBy) {
sortMap.get(sortBy);
....
}
}
Which method do you think looks best?
Or is there a better way than the ones listed above??
If you have any good comments, we would appreciate your feedback!
You can use Spring's GetMapping.params() to specify mapping to be invoked for certain request parameter value to get rid of if-else construct entirely.
It's an alias for RequestMapping.params().
The parameters of the mapped request, narrowing the primary mapping.
Same format for any environment: a sequence of "myParam=myValue" style expressions, with a request only mapped if each such parameter is found to have the given value.
Then you would need to define additional mapping for each possible value of the parameter, spring will take care of invoking correct mapping, depending on the value of sortby.
#GetMapping(params = "sortby=latest")
public ResponseEntity<List<StudyGroupResponseDTO>> findStudyGroupSortByLatest() {
//logic for sort by latest
}
#GetMapping(params = "sortby=star")
public ResponseEntity<List<StudyGroupResponseDTO>> findStudyGroupSortByStar() {
//logic for sort by star
}
I am using below DTO class with respective annotations and are working fine also. But when I send a integer value for name/reqID(which is a String datatype) fields, still it is executing without any error/exception. How to avoid it or validate the datatype of incoming fields.
public class RequestDTO {
#NotEmpty(message = "Please provide reqID")
private String reqID;
#NotEmpty(message = "Please provide name")
private String name;
private Map <String, String> unknownProperties;
public AccountDTO(){
this.unknownProperties = new HashMap<String, String>();
}
public AccountDTO(String reqID, String name){
this.reqID= reqID;
this.name = name;
this.unknownProperties = new HashMap<String, String>();
}
#JsonAnySetter
public void add(String key, String value) {
this.unknownProperties.put(key, value);
}
#JsonAnyGetter
public Map <String, String> getUnknownProperties() {
return unknownProperties;
}
//getters and setters
}
working for { "reqID" : 56, "name" : 674 }. Have to check the datatype/reject the request. Any help would be appreciable.
If you're using Spring boot, by default it uses Jackson to parse JSON. There's no configuration option within Jackson to disable this feature
Here you will find interesting approaches to solving this problem:
Disable conversion of scalars to strings when deserializing with Jackson
You can disable MapperFeature ALLOW_COERCION_OF_SCALARS which is enabled by default.
Then conversions from JSON String are not allowed.
Doc Details here
public static final MapperFeature ALLOW_COERCION_OF_SCALARS
When feature is disabled, only strictly compatible input may be bound:
numbers for numbers, boolean values for booleans. When feature is
enabled, conversions from JSON String are allowed, as long as textual
value matches (for example, String "true" is allowed as equivalent of
JSON boolean token true; or String "1.0" for double).
Or create a custom json deserializer for string overriding default serializer JsonDeserializer<String>.
You could validate the input you are getting. But this is not specific to your DTO so if you have some sort of Utilities class with static methods (think about having one if you don't) it's better if you add it there and grab it for any DTO that might need this validation.
The validation method would look something like this:
public static boolean isNumber(String in) {
try{
Integer.parseInt(in);
// log something useful here
return true;
} catch(NumberFormatException e) {
return false;
}
}
You could then use this method throw your own exception. Then handle that the way you'd need:
if (Utilities.isNumber(reqID)){
throw new IllegalArgumentException("Meaningful Exception Message here");
}
I hope it helps! :)
Spring boot allows regular expression checking using #Patter annotation. So just add the following
#Pattern(regexp="[a-zA-Z]")
#NotEmpty(message = "Please provide name")
private String name;
Is it possible to dynamically create a GraphQL schema ?
We store the data in mongoDB and there is a possibility of new fields getting added. We do not want any code change to happen for this newly added field in the mongoDB document.
Is there any way we can generate the schema dynamically ?
Schema is defined in code, but for java(schema as pojo), when new
attribute is added, you have to update and recompile code, then
archive and deploy the jar again. Any way to generate schema by the
data instead of pre-define it?
Currently we are using java related projects (graphql-java, graphql-java-annotations) for GraphQL development.
You could use graphql-spqr, it allows you auto-generate a schema based on your service classes. In your case, it would look like this:
public class Pojo {
private Long id;
private String name;
// whatever Ext is, any (complex) object would work fine
private List<Ext> exts;
}
public class Ext {
public String something;
public String somethingElse;
}
Presumably, you have a service class containing your business logic:
public class PojoService {
//this could also return List<Pojo> or whatever is applicable
#GraphQLQuery(name = "pojo")
public Pojo getPojo() {...}
}
To expose this service, you'd just do the following:
GraphQLSchema schema = new GraphQLSchemaGenerator()
.withOperationsFromSingleton(new PojoService())
.generate();
You could then fire a query such as:
query test {
pojo {
id
name
exts {
something
somethingElse
} } }
No need for strange wrappers or custom code of any kind, nor sacrificing type safety. Works with generics, dependency injection, or any other jazz you may have in your project.
Full disclosure: I'm the author of graphql-spqr.
After some days' investigation. I found it is hard to generate schema dynamically in Java (or cost is so high).
Well, from another way. I think we can use Map as a compromised way to accomplish that.
POJO/Entity
public class POJO{
#GraphQLField
private Long id;
#GraphQLField
private String name;
// ...
#GraphQLField
private GMap exts;
}
GMap is a customized Map (Because Map/HashMap is a JDK inner class which could not make as GraphQL Schema but only extend).
GMap
public class GMap extends HashMap<String, String> {
#GraphQLField
public String get(#GraphQLName("key") String key) {
return super.get(key);
}
}
Retrieve data from Client
// query script
query test
{
your_method
{
id
name
exts {
get(key: "ext") // Add a extended attribute someday
}
}
}
// result
{
"errors":[],
"data":
{
"list":
[
{"id":1, name: "name1", exts: {"get": "ext1"}},
{"id":2, name: "name2", exts: {"get": "ext2"}}
]
}
}
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);
}
I have a java class which has one field with getter and setter, and a second pair of getter and setter that access this field in another way:
public class NullAbleId {
private static final int NULL_ID = -1;
private int internalId;
getter & setter for internalId
public Integer getId() {
if(this.internalId == NULL_ID) {
return null;
} else {
return Integer.valueOf(internalId);
}
}
public void setId(Integer id) {
if (id == null) {
this.internalId = NULL_ID;
} else {
this.internalId = id.intValue();
}
}
}
(the reason for this construction is that I want to build a way to hande Nullable Intergers)
On the Flash/Flex client side, I have a Class with two properties: id and internalId (the id properties are only for testing, at the end they should return the internalId value)
BlazeDS seams to transfer both values: id and internalId, because both have a complete getter setter pair. I want Blaze not to transfer id, only internalId should be transferred. But I have no idea how I have to configure that.
All the rules for BlazeDS serialization are here:
http://livedocs.adobe.com/blazeds/1/blazeds_devguide/help.html?content=serialize_data_3.html
Here is a quote: "Fields that are static, transient, or nonpublic, as well as bean properties that are nonpublic or static, are excluded."
So if you can make your id property fit that criteria it will be excluded. Another option would be to create a custom serializer that overtly does not include your id property.
All the best,
~harris
Besides transient / marshaller you can implement the Externalizable interface and create your custom serialization.
See serialization rules
It maybe a little bit old, but it could help some : there is a nice ticket about excluding properties from Java to Flex via BlazeDS
EDIT : a better soluce, it's to use the #AmfIgnore (or #AmfIgnoreField if your serialization is directly on the fields) annotation present in the spring-flex-core.jar (I've used the 1.5.2-RELEASE)