How can I refactor similar configuration classes in Spring Boot? - java

I have multiple similar configuration classes in my project.
#Configuration
public class configA {
private final static String CONFIG_STRING = "configA";
#Bean( name = "first" + CONFIG_STRING )
public First first() {
...
}
#Bean( name = "second" + CONFIG_STRING )
public Second second() {
...
}
}
#Configuration
public class configB {
private final static String CONFIG_STRING = "configB";
#Bean( name = "first" + CONFIG_STRING )
public First first() {
...
}
#Bean( name = "second" + CONFIG_STRING )
public Second second() {
...
}
}
They're all the same, only the CONFIG_STRING is different.
Can I refactor those classes?

You can use Template Method in order to let subclasses override specific steps of the algorithm without changing its structure.
Here is an example below. If there is a method that keeps the shared behaviour, this approach would also be a good option.
BaseConfig:
#Configuration
public abstract class BaseConfig {
protected abstract String first();
protected abstract String second();
}
ConfigA:
#Configuration
public class ConfigA extends BaseConfig {
private final static String CONFIG_STRING = "configA";
#Override
#Bean( name = "first" + CONFIG_STRING )
protected String first() {
return CONFIG_STRING;
}
#Override
#Bean( name = "second" + CONFIG_STRING )
protected String second() {
return CONFIG_STRING;
}
}
ConfigB:
#Configuration
public class ConfigB extends BaseConfig {
private final static String CONFIG_STRING = "configB";
#Override
#Bean( name = "first" + CONFIG_STRING )
protected String first() {
return CONFIG_STRING;
}
#Override
#Bean( name = "second" + CONFIG_STRING )
protected String second() {
return CONFIG_STRING;
}
}

Related

Load/Map array of object with Spring #PropertySource

I would like to read the array in the YAML file in java spring. I don't know if it's possible to use an array of objects.
Here is the content of the YAML:
main:
nodes:
-
label: "My label"
id: "My label id"
uri: ""
-
label: "My label"
id: "My label id"
uri: ""
And here is my component which loads the file:
public class MyNode {
String label, id, uri;
}
#ConfigurationProperties(prefix = "main")
#PropertySource(value = "classpath:mycustomfile.yml", factory = YamlPropertyLoaderFactory.class)
#Component
public class CustomProperties {
private List<MyNode> nodes;
}
And the YamlPropertySourceFactory
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
When I run my application, nodes are null. I followed Baeldun tutorial
I solved it by adding #Data (Lombok) and make MyNode as inner static class. You can also add getter and setter if you don't use Lombok.
#ConfigurationProperties(prefix = "main")
#PropertySource(value = "classpath:mycustomfile.yml", factory = YamlPropertyLoaderFactory.class)
#Component
public class CustomProperties {
private List<MyNode> nodes;
#Data
public static class MyNode {
private String label, id, uri;
}
}
It seems that the problem comes from the visibility of your MyNode class members.
If you define public setters for all the members, everything will work fine.
So just try to modify
public class MyNode {
String label, id, uri;
}
to
public class MyNode {
private String label, id, uri;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
}

setName and received always null

I'm new in Java :] and I have a little problem with my app:
Why when I run it, it keeps saying me "null" even when person.setname("John")
I tried to fix it but without good result, what is wrong here and why?
I tried to debug it - same result - setName set name to John but anyway it keeps printing "null"
Really strange for new user like me.
If someone can help, or even try to say me what's wrong i'd be glad, thanks.
class App {
public static void main(String[] args) throws InterruptedException {
List<String> blacklist = Arrays.asList("Bill, Adam, Jessie");
;
Person person = new PersonWithBlacklistedCheck(
new PersonWithNullCheck(new Person()),
blacklist);
person.setName("John");
System.out.println("Person: " + person);
}
}
class PersonWithBlacklistedCheck extends Person {
private final List<String> blacklist;
private final Person target;
PersonWithBlacklistedCheck(Person target, List<String> blacklist) {
this.target = target;
this.blacklist = blacklist;
}
#Override
public String getName() {
return target.getName();
}
#Override
public void setName(String name) {
if (this.blacklist.contains(name)) {
throw new RuntimeException("[" + name + "] cannot be used as a name! it is blacklisted");
}
target.setName(name);
}
}
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class PersonWithNullCheck extends Person {
private final Person target;
PersonWithNullCheck(Person target) {
this.target = target;
}
#Override
public String getName() {
return target.getName();
}
#Override
public void setName(String name) {
if (name == null) {
throw new RuntimeException("[name] must not be null!!");
}
target.setName(name);
}
}
You have person object containing another person called "target" and another one "target" :
Blacklist should look like this:
List<String> blacklist = Arrays.asList("Bill", "Adam", "Jessie");
You were creating one entry: "Bill, Adam, Jessie".
You were doing some unnecessary metods override, adding unnecessary objects - when you extend class you have that object "person" already there, you don't need to put it as another object "target". If you want to have both null check and blacklist check executed before setting name you can extend classes in hierarchy: Person -> PersonWithNullCheck -> PersonWithBlacklistedCheck. Now setName() method will be executed in each of classes as ordered. Here's a fixed solution:
public class Test {
public static void main(String[] args) {
List<String> blacklist = Arrays.asList("Bill", "Adam", "Jessie");
Person person = new PersonWithBlacklistedCheck(blacklist);
person.setName("John");
System.out.println("Person: " + person);
}
}
class PersonWithBlacklistedCheck extends PersonWithNullCheck {
private final List<String> blacklist;
PersonWithBlacklistedCheck(List<String> blacklist) {
this.blacklist = blacklist;
}
#Override
public void setName(String name) {
if (this.blacklist.contains(name)) {
throw new RuntimeException("[" + name + "] cannot be used as a name! it is blacklisted");
}
super.setName(name);
}
}
class PersonWithNullCheck extends Person {
#Override
public void setName(String name) {
if (name == null) {
throw new RuntimeException("[name] must not be null!!");
}
super.setName(name);
}
}
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}

Error "400 Bad Request" The request sent by the client was syntactically incorrect

I am new to jQuery and trying to call Web-Service using Ajax. It seems fairly simple code but somehow not able to understand the reason for its failure. I tried all possible ways (that I could think of) but somehow not getting it to work.
Jsp code:
function tmpData() {
var dataObject = JSON.stringify({
'empfname': "First Name",
'emplname': "Last Name"
});
alert("dataObject=" + dataObject);
$.ajax({
url:"http://localhost:8080/OnlineStore/kmsg/grocery/tmpinfo",
type:"POST",
contentType: 'application/json',
data: dataObject,
done: setData,
fail: displayError()
});
}; // end of function
Controller:
#RestController
#RequestMapping("/kmsg/grocery")
public class TmpSvcImpl implements TmpSvcInt {
#Override
#RequestMapping(value = "/tmpinfo", method = RequestMethod.POST, headers = "Accept=application/json")
public #ResponseBody Map<String, Object> setData(#RequestBody final Emp employee1) throws Exception {
System.out.println("employee1=" + employee1);
String fname = employee1.getEmpfname();
String lname = employee1.getEmplname();
System.out.println("fn=" + fname ) ;
System.out.println("ln=" + lname ) ;
return null;
}
}
Model class:
public class Emp implements Serializable {
private static final long serialVersionUID = 1L;
String empfname;
String emplname;
public String getEmpfname() {
return empfname;
}
public void setEmpfname(String empfname) {
this.empfname = empfname;
}
public String getEmplname() {
return emplname;
}
public void setEmplname(String emplname) {
this.emplname = emplname;
}
public Emp(String fn, String ln){
this.empfname = fn ;
this.emplname = ln ;
}
#Override
public String toString() {
return "Emp {empfname=" + empfname + ", emplname=" + emplname + "}" ;
}
}
Add default constructor to your Emp class so that Jackson is able to create an instance of it, as follows:
public class Emp implements Serializable {
private static final long serialVersionUID = 1L;
public String empfname;
public String emplname;
public String getEmpfname() {
return empfname;
}
public void setEmpfname(String empfname) {
this.empfname = empfname;
}
public String getEmplname() {
return emplname;
}
public void setEmplname(String emplname) {
this.emplname = emplname;
}
public Emp(String fn, String ln) {
this.empfname = fn;
this.emplname = ln;
}
/**
* default constructor
*/
public Emp() {
}
#Override
public String toString() {
return "Emp {empfname=" + empfname + ", emplname=" + emplname + "}";
}
}
In the block you've described:
#RestController
#RequestMapping("/kmsg/grocery")
public class TmpSvcImpl implements TmpSvcInt {
#Override
#RequestMapping(
value = "/tmpinfo",
method = RequestMethod.POST,
headers = "Accept=application/json")
public #ResponseBody Map<String, Object> setData(#RequestBody final Emp employee1) throws Exception {
System.out.println("employee1=" + employee1);
String fname = employee1.getEmpfname();
String lname = employee1.getEmplname();
System.out.println("fn=" + fname ) ;
System.out.println("ln=" + lname ) ;
return null;
}
}
Specifying the 'Accept' header for this usage case is not really correct. If you want this method to respond only when the content-type is 'application/json' then you will have to add a consumes attribute of 'application/json'. As a comment pointed out this is not strictly necessary. Adding a produces attribute of 'application/json' will ensure that Spring will attempt to marshal the returned content as a JSON structure.
#RestController
#RequestMapping("/kmsg/grocery")
public class TmpSvcImpl implements TmpSvcInt {
#Override
#RequestMapping(
value = "/tmpinfo",
method = RequestMethod.POST,
consumes = "application/json",
produces = "application/json")
public #ResponseBody Map<String, Object> setData(#RequestBody final Emp employee1) throws Exception {
System.out.println("employee1=" + employee1);
String fname = employee1.getEmpfname();
String lname = employee1.getEmplname();
System.out.println("fn=" + fname ) ;
System.out.println("ln=" + lname ) ;
return null;
}
}

Two Enums implementing the same interface sharing a method

I have two Enums implementing the same interface
public interface MetaEnum{
String getDefaultValue();
String getKey();
}
public enum A implements MetaEnum{
ONE("1"), TWO("2");
private String val;
A(final v){
val = v;
}
#Override
public String getDefaultValue() {
return value;
}
#Override
public String getKey() {
return (this.getClass().getPackage().getName() + "." + this.getClass().getSimpleName() + "." +
this.toString()).toLowerCase();
}
}
public enum B implements MetaEnum{
UN("1"), DEUX("2");
private String val;
B(final v){
val = v;
}
#Override
public String getDefaultValue() {
return value;
}
#Override
public String getKey() {
return (this.getClass().getPackage().getName() + "." + this.getClass().getSimpleName() + "." +
this.toString()).toLowerCase();
}
...other methods specific to this enum
}
I have duplicated code and I would like to avoid it. Is there a way to implement getKey in a sort of abstract class? I looked at this question Java Enum as generic type in Enum but it cannot adapt it to what I need.
The default method from Java 8 should help you :)
This feature allows you to implement method inside an interface.
public interface MetaEnum {
String getValue();
default String getKey() {
return (this.getClass().getPackage().getName() + "." +
this.getClass().getSimpleName() + "." +
this.toString()).toLowerCase();
}
}
Unfortunately, you can't implement a default method for getters, so you still have some duplicate code.
Extract the common code to the separate class:
class MetaEnumHelper {
private String val;
MetaEnumImpl(final v){
val = v;
}
public String getDefaultValue() {
return value;
}
public String getKey(MetaEnum metaEnum) {
return (metaEnum.getClass().getPackage().getName() + "." + metaEnum.getClass().getSimpleName() + "." +
metaEnum.toString()).toLowerCase();
}
}
public enum A implements MetaEnum{
ONE("1"), TWO("2");
private MetaEnumHelper helper;
A(final v){
helper = new MetaEnumHelper(v);
}
#Override
public String getDefaultValue() {
return helper.getDefaultValue();
}
#Override
public String getKey() {
return helper.getKey(this);
}
}
public enum B implements MetaEnum{
UN("1"), DEUX("2");
private MetaEnumHelper helper;
B(final v){
helper = new MetaEnumHelper(v);
}
#Override
public String getDefaultValue() {
return helper.getDefaultValue();
}
#Override
public String getKey() {
return helper.getKey(this);
}
...other methods specific to this enum
}

How does JavaBeans Property Adapter work?

What I'm trying to do works fine if I follow the JavaFX property definition described here.
Now, instead, I want to define properties from Java Beans objects using Java Beans Property Adapter. Since there is no documentation I can't figure out how it works.
Suppose I have a simple POJO class:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
}
and a PersonProperty:
public class PersonProperty {
private Person person = new Person();
private JavaBeanStringProperty name;
public PersonProperty() throws NoSuchMethodException {
name = JavaBeanStringPropertyBuilder.create().bean( person ).name( "name" ).build();
}
public Person getPerson() {
return person;
}
public void setPerson( Person person ) {
this.person = person;
}
public JavaBeanStringProperty nameProperty() {
return name;
}
}
and finally a test:
public void personTest() throws NoSuchMethodException {
PersonProperty pp = new PersonProperty();
pp.getPerson().setName( "A" );
pp.getPerson().setName( "B" );
pp.nameProperty().addListener( new ChangeListener<String>() {
#Override
public void changed( ObservableValue<? extends String> ov, String t, String t1 ) {
System.out.println( "from " + t + " to " + t1 );
}
} );
pp.getPerson().setName( "C" );
pp.getPerson().setName( "D" );
}
I'm expecting to see:
from B to C
from C to D
Instead nothing appears.
If I add pp.nameProperty().set("E") at the end of personTest I get from B to E
I think the issue here is that Person is indeed a POJO, but not a JavaBean: It is missing the hooks for PropertyChangeListeners. Java will not magically know when Person#name changes. Instead, the JavaFX adapter will look for a way to add a PropertyChangeListener and listen to events for a property called 'name'. If you add a PropertyChangeSupport instance to your Person class it will work as expected:
public class Person {
private String name;
private PropertyChangeSupport _changeSupport;
public Person() {
_changeSupport = new PropertyChangeSupport(this);
}
public String getName() {
return name;
}
public void setName( String name ) {
final String prev = this.name;
this.name = name;
_changeSupport.firePropertyChange("name", prev, name);
}
public void addPropertyChangeListener(final PropertyChangeListener listener) {
_changeSupport.addPropertyChangeListener(listener);
}
}

Categories