Please note: I am using Spring Boot here, not Spring MVC.
Java 8 and Spring Boot/Jackson here. I have the following enum:
public enum OrderType {
PARTIAL("partialOrder"),
FULL("fullOrder");
private String label;
OrderType(String label) { this.label = label; }
}
I would like to expose a POST endpoint where the client can place the OrderType#label as a request parameter, and Spring will know to convert the provided label into an OrderType like so:
#PostMapping("/v1/myapp/orders")
public ResponseEntity<?> acceptOrder(#RequestParam(value = "orderType") OrderType orderType) {
// ...
}
And hence the client could make a call such as POST /v1/myapp/orders?orderType=fullOrder and on the server, the controller would receive an OrderType instance.
How can I accomplish this?
This was so easy, a cave man could even do it.
Enum:
public class MyEnum {
FIZZ("sumpin"),
BUZZ("sumpinElse");
#JsonValue
private String label;
MyEnum(String label) { this.label = label; }
public String getLabel() { return this.label; }
public static Optional<MyEnum> toMyEnum(String label) {
if (label == null) {
return Optional.empty();
}
for (MyEnum mine : MyEnum.values()) {
if (label.equals(mine.getLabel()) {
return Optional.of(mine);
}
}
throw new IllegalArgumentException("no supported");
}
}
Spring converter:
public class MyEnumConverter implements Converter<String,MyEnum> {
#Override
public MyEnum convert(String label) {
Optional<MyEnum> maybeMine = MyEnum.toMyEnum(label);
if (maybeMine.isPresent()) {
return maybeMine.get():
}
// else, you figure out what you want your app to do,
// thats not my job!
}
}
Register it:
#Configuration
public class YourAppConfig {
#Autowired
public void configureConverter(FormatterRegistry registry) {
registry.addConverter(new MyEnumConverter());
}
}
Support it from inside in a controller/resource:
#Post("/v1/foobar/doSomething")
public ResponseEntity<?> doSomething(#RequestParam(value = "mine") MyEnum mine) {
// ... whatever
}
Use the darn thing in an API call:
POST http://yourlousyapp.example.com/v1/foobar/doSomething?mine=sumpin
Related
I have the Feature class as follows:
public class Feature {
private FeatureType featureType;
private Integer featureNumber;
#JsonProperty("isActive")
private boolean isActive;
#JsonProperty("isAutomatedFeature")
private boolean isAutomatedFeature;
private Feature() {
}
public FeatureType getFeatureType() {
return featureType;
}
public Integer getFeatureNumber() {
return featureNumber;
}
#JsonProperty("isActive")
public boolean isActive() {
return isActive;
}
#JsonProperty("isAutomatedFeature")
public boolean isAutomatedFeature() {
return isAutomatedFeature;
}
public static class Builder {
Feature feature=null;
public Builder() {
feature=new Feature();
}
public Builder setFeatureType(FeatureType featureType) {
feature.featureType=featureType;
return this;
}
public Builder setFeatureNumber(Integer featureNumber) {
feature.featureNumber=featureNumber;
return this;
}
#JsonProperty("isActive")
public Builder setIsActive(boolean isActive) {
feature.isActive=isActive;
return this;
}
#JsonProperty("isAutomatedFeature")
public Builder setIsCarryForwardAllowed(boolean isAutomatedFeature) {
feature.isAutomatedFeature=isAutomatedFeature;
return this;
}
public Feature build() {
return feature;
}
}
Now to create a feature we have an api called
api/v1/feature---POST and its payload will look like below:
"feature":{
"featureType":"XYZ",
"featureType":"40",
"isActive" :true,
"isAutomatedFeature" :true
}
But we can't call this api directly to create a feature so we use RestAssured RequestSpecificationImpl to hit the above API.
It will be something like this
public Response create(Feature feature) {
return post("api/v1/feature",feature);
}
Internally it creates the payload/body for the url and hit the api.So to hit the above method create we need the feature object which I created using builder.
Feature feature=new Feature.Builder().setFeatureType("XYZ").setFeatureNumber(40).setIsActive(true).setIsCarryForwardAllowed(true).build();
I expected the payload to be like above mentioned payload.
But the generated payload is something like this:
"feature":{
"featureType":"XYZ",
"featureType":"40",
"Active" :true,
"AutomatedFeature" :true
}
i.e it removes "is" from "isActive" and "isAutomatedFeature" and make them "Active" and "AutomatedFeature" respectively even when I am using #JsonProperty,so the api fails.
Can anyone guide me what I am doing wrong or how it can be resolved.
Thanks in advance.
I have a class like this:
public class SampleDto {
private String normalProperty1;
private String normalProperty2;
private String normalProperty3;
private String sensitiveProperty1;
private String sensitiveProperty2;
public String getNormalProperty1() {
return normalProperty1;
}
public void setNormalProperty1(String normalProperty1) {
this.normalProperty1 = normalProperty1;
}
public String getNormalProperty2() {
return normalProperty2;
}
public void setNormalProperty2(String normalProperty2) {
this.normalProperty2 = normalProperty2;
}
public String getNormalProperty3() {
return normalProperty3;
}
public void setNormalProperty3(String normalProperty3) {
this.normalProperty3 = normalProperty3;
}
public String getSensitiveProperty1() {
return sensitiveProperty1;
}
public void setSensitiveProperty1(String sensitiveProperty1) {
this.sensitiveProperty1 = sensitiveProperty1;
}
public String getSensitiveProperty2() {
return sensitiveProperty2;
}
public void setSensitiveProperty2(String sensitiveProperty2) {
this.sensitiveProperty2 = sensitiveProperty2;
}
}
There are parts in the application where i need to serialize it as it is because the object is in a secure environment.
But i need to store the json in a db and store it without the sensitiveProperties, I can't just ignore the properties because they are needed in the other processes.
I was thinking to use Jackson views to solve the problem but i don't know if there is something special in Jackson where I can say, every json object that has the property "sensitiveProperty1" set it to null.
I'm using Java and Jackson
I think that this site covers what you're looking for pretty well.
Essentially what you'll want to do is to add #JsonIgnoreProperties(value = { "intValue" }) at the class level or #JsonIgnore at the field level and then Jackson should take care of the rest for you.
In your case that would look something like:
public class SampleDto {
#JsonIgnore
private String normalProperty1;
private String normalProperty2;
...
again a small problem by understanding "how tapestry works".
I've got a Tapestry component (in this case a value encoder):
public class EditionEncoder implements ValueEncoder<Edition>, ValueEncoderFactory<Edition> {
#Inject
private IEditionManager editionDao;
public EditionEncoder(IEditionManager editionDao) {
this.editionManager = editionDao;
}
#Override
public String toClient(Edition value) {
if(value == null) {
return "";
}
return value.getName();
}
#Override
public Edition toValue(String clientValue) {
if(clientValue.equals("")) {
return null;
}
return editionManager.getEditionByName(clientValue);
}
#Override
public ValueEncoder<Edition> create(Class<Edition> type) {
return this;
}
}
Injecting the the Manager is not working, because the Encoder is created within a page like that:
public void create() {
editionEncoder = new EditionEncoder();
}
casued by this, i'm forced to use this ugly solution:
#Inject
private IEditionManager editionmanager;
editionEncoder = new EditionEncoder(editionManager);
Is there a better way to inject components during runtime or is there a better solution in general for it?
Thanks for your help in advance,
As soon as you use "new" then tapestry-ioc is not involved in object creation and can't inject. You should inject everything and never use "new" for singleton services. This is true for all ioc containers, not just tapestry-ioc.
Also if you put #Inject on a field then you don't also need a constructor to set it. Do one or the other, never both.
You should do something like this:
public class MyAppModule {
public void bind(ServiceBinder binder) {
binder.bind(EditionEncoder.class);
}
}
Then in your page/component/service
#Inject EditionEncoder editionEncoder;
If you wanted to put your own instantiated objects in there you can do
public class MyServiceModule {
public void bind(ServiceBinder binder) {
binder.bind(Service1.class, Service1Impl.class);
binder.bind(Service2.class, Service2Impl.class);
}
public SomeService buildSomeService(Service1 service1, Service2 service2, #AutoBuild Service3Impl service3) {
Date someDate = new Date();
return new SomeServiceImpl(service1, service2, service3, someDate);
}
}
I am not being able to make messageSource work in the Pojo classes,its throwing a nullpointerexception. However in all the other classes namely controller,service messageSource is working alright. Could someone please suggest what needs to be done ?
#Autowired
private MessageSource messageSource;
I have autowired the MessageSource using the above code snippet.
public class ProposalWiseSelectionForm implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Autowired
private MessageSource messageSource;
private String txtPageHierarchy="";
private String txtLineOfBusiness;
private String txtProduct;
private String btn;
private String clickedGo="N";
private List arrLineOfBusiness=new ArrayList();
private List arrProduct=new ArrayList();
#Valid
private ArrayList documentList=initiateDocumentList();
private String txtPageMode="I";
private String enableDiscardBtn="N";
private String enableInsertBtn="N";
private String isDivVisible="N";
private int numApplicationType=1;
public ProposalWiseSelectionForm() {
}
public String getTxtPageHierarchy() {
return txtPageHierarchy;
}
public void setTxtPageHierarchy(String txtPageHierarchy) {
this.txtPageHierarchy = txtPageHierarchy;
}
public String getTxtLineOfBusiness() {
return txtLineOfBusiness;
}
public void setTxtLineOfBusiness(String txtLineOfBusiness) {
this.txtLineOfBusiness = txtLineOfBusiness;
}
public String getTxtProduct() {
return txtProduct;
}
public void setTxtProduct(String txtProduct) {
this.txtProduct = txtProduct;
}
public String getBtn() {
return btn;
}
public void setBtn(String btn) {
this.btn = btn;
}
public String getClickedGo() {
return clickedGo;
}
public void setClickedGo(String clickedGo) {
this.clickedGo = clickedGo;
}
public List getArrLineOfBusiness() {
return arrLineOfBusiness;
}
public void setArrLineOfBusiness(List arrLineOfBusiness) {
this.arrLineOfBusiness = arrLineOfBusiness;
}
public List getArrProduct() {
return arrProduct;
}
public void setArrProduct(List arrProduct) {
this.arrProduct = arrProduct;
}
public void setArrProduct(ArrayList arrProduct) {
this.arrProduct = arrProduct;
}
public ArrayList getDocumentList() {
return documentList;
}
public void setDocumentList(ArrayList documentList) {
this.documentList = documentList;
}
public String getTxtPageMode() {
return txtPageMode;
}
public void setTxtPageMode(String txtPageMode) {
this.txtPageMode = txtPageMode;
}
public String getEnableDiscardBtn() {
return enableDiscardBtn;
}
public void setEnableDiscardBtn(String enableDiscardBtn) {
this.enableDiscardBtn = enableDiscardBtn;
}
public String getEnableInsertBtn() {
return enableInsertBtn;
}
public void setEnableInsertBtn(String enableInsertBtn) {
this.enableInsertBtn = enableInsertBtn;
}
public String getIsDivVisible() {
return isDivVisible;
}
public void setIsDivVisible(String isDivVisible) {
this.isDivVisible = isDivVisible;
}
public int getNumApplicationType() {
return numApplicationType;
}
public void setNumApplicationType(int numApplicationType) {
this.numApplicationType = numApplicationType;
}
}
In order to be able to use #Autowired in a class, that class has to be managed by Spring.
of
Your ProposalWiseSelectionForm class is obviously not managed by Spring and therefor messageSource is always null.
Using #Autowired MessageSource messageSource in your other classes works, because as you mention those classes are managed by Spring (as you have mentioned they are either controllers, services etc).
I am guessing that ProposalWiseSelectionForm is a DTO used to capture values from a form. The sort of class will not be a Spring bean and therefor you can't autowire stuff into it.
I suggest you either move the logic you need out of the DTO and into the controller (or some Spring managed utility) or in the extreme case that you absolutely need #Autowired in the DTO, take a look at #Configurable here and here
Try using #Component,you might be getting this issue because of the fact the Pojo class is not being recognized.
You have to make your class a Spring bean
Add #Component annotation to your class and add these 2 lines to your appContext.xml:
<context:component-scan base-package="com.<your-company-name>" />
<context:annotation-config />
Or just add the service in your beans section in the appContext.xml if you wish not to work with Spring component-scan feature.
Maybe I missunderstood JavaFX binding or there is a bug in SimpleStringProperty.
When I run this testcode my changed model value didn't get the new value. Test testBindingToModel fails. I thought my model should then be updated with the value of the TextField tf. But only the binding value of prop1Binding gets the value "test".
public class BindingTest {
private TextField tf;
private Model model;
private ModelBinding mb;
#Before
public void prepare() {
tf = new TextField();
model = new Model();
mb = new ModelBinding(model);
Bindings.bindBidirectional(tf.textProperty(), mb.prop1Binding);
}
#Test
public void testBindingToMB() {
tf.setText("test");
assertEquals(tf.getText(), mb.prop1Binding.get());
}
#Test
public void testBindingToModel() {
tf.setText("test");
assertEquals(tf.getText(), mb.prop1Binding.get());
assertEquals(tf.getText(), model.getProp1());
}
private static class ModelBinding {
private final StringProperty prop1Binding;
public ModelBinding(Model model) {
prop1Binding = new SimpleStringProperty(model, "prop1");
}
}
private static class Model {
private String prop1;
public String getProp1() {
return prop1;
}
public void setProp1(String prop1) {
this.prop1 = prop1;
}
}
}
Thanks for your help.
Best regards
Sebastian
EDIT:
With this class I can set the value of the model directly. I will test this class in the next days and comment on this post with my result.
public class MySimpleStringProperty extends SimpleStringProperty {
public MySimpleStringProperty(Object obj, String name) {
super(obj, name);
}
public MySimpleStringProperty(Object obj, String name, String initVal) {
super(obj, name, initVal);
}
#Override
public void set(String arg0) {
super.set(arg0);
if (this.getBean() != null) {
try {
Field f = this.getBean().getClass().getDeclaredField(this.getName());
f.setAccessible(true);
f.set(this.getBean(), arg0);
} catch (NoSuchFieldException e) {
// logging here
} catch (SecurityException e) {
// logging here
} catch (IllegalArgumentException e) {
// logging here
} catch (IllegalAccessException e) {
// logging here
}
}
}
}
This constructor doesn't attach SimpleStringProperty to a bean object unfortunately. It just says to SimpleStringProperty which bean property belongs to.
E.g., if you want to have a property in your class you should do it next way:
public static class Model {
private StringProperty prop1 =
new SimpleStringProperty(this, "prop1", "default_value");
public String getProp1() {
return prop1.get();
}
public void setProp1(String value) {
prop1.set(value);
}
public StringProperty prop1Property() {
return prop1;
}
}
Note, that there is no way to bind to your original Model class as it provides no events about setting new prop1 value. If you want to have observable model, you should use fx properties from the beginning.
Just figured out that there is provided the class JavaBeanStringProperty, which just fullfill my request.
Using this code I can directly bind the value of my bean to a StringProperty (included setting and getting of my value to / from my Bean).
binding = JavaBeanStringPropertyBuilder.create().beanClass(Model.class).bean(model).name("prop1").build();
The only problem I found is that when you change the value of the model after setting the binding, there is no update e.g. in the TextField.