I have implemented service logic using builder pattern like below in a Spring boot application.
#Service
public class EmailServiceImpl implements NotificationService{
private Map<String, Object> emailTemplateContext;;
private List<String> recipients;
private String templateName;
private String subjectName;
private List<String> ccRecipients;
public EmailServiceImpl() {
}
private EmailServiceImpl(String templateName, String subjectName, List<String> recipients,
List<String> ccRecipients,
Map<String, Object> emailTemplateContext) {
this.emailTemplateContext = emailTemplateContext;
this.recipients = recipients;
this.templateName = templateName;
this.subjectName = subjectName;
this.ccRecipients = ccRecipients;
}
public static class EmailBuilder {
private Map<String, Object> emailTemplateContext = new HashMap<String, Object>();
private List<String> recipients;
private String templateName;
private String subjectName;
private List<String> ccRecipients;
public EmailBuilder withTemplateName(String templateName) {
this.templateName = templateName;
return this;
}
public EmailBuilder withSubjectName(String subjectName) {
this.subjectName = subjectName;
return this;
}
public EmailBuilder withTemplateContextValues(String key, String value) {
this.emailTemplateContext.put(key, value);
return this;
}
public EmailBuilder withTemplateContextValues(String key,List< String> value) {
this.emailTemplateContext.put(key, value);
return this;
}
public EmailBuilder withRecipients(List<String> recipients) {
this.recipients = recipients;
return this;
}
public EmailBuilder withCCRecipients(List<String> ccRecipients) {
this.ccRecipients = ccRecipients;
return this;
}
public EmailServiceImpl build() {
return new EmailServiceImpl(templateName, subjectName, recipients, ccRecipients, emailTemplateContext);
}
}
public void send() {
// implement email send logic
}
}
Then this will be injected in another class something like below
public class EmailSender{
private EmailBuilder emailBuilder;
EmailSender(EmailBuilder emailBuilder){
this.emailBuilder=emailBuilder;
}
public void sendEmail(){
this.emailBuilder
.withRecipients(executionRunBO.getEmailRecipients().stream().map(email -> email.getEmail())
.collect(Collectors.toList()))
.withSubjectName("Reports Draft ").withTemplateName("/emails/reports.ftlh")
.withTemplateContextValues("userName", "TestUser").build().send();
}
}
There are few questions related to the Builder pattern with Spring.
Since #service constructor is private Spring framework can not initiate the bean class.To avoid getting initialization exception i have made constructor to public but it is not the builder pattern.So how do i implement builder pattern correctly with Spring framework?
How do i call the EmailServiceImpl class's EmailBuilder outside from the service class? Since in my examples i have injected EmailBuilder as a constructor argument then Spring framework throws that
required a bean of type '...EmailServiceImpl$EmailBuilder' that could
not be found.
Please help me to clarify those things.
You are mixing a lot of functionality together here. I would make this more streamlined.
I would create a dto class Email, not a Spring bean.
public class Email {
private Map<String, Object> emailTemplateContext;
private List<String> recipients;
private String templateName;
private String subjectName;
private List<String> ccRecipients;
private Email(String templateName, String subjectName, List<String> recipients,
List<String> ccRecipients,
Map<String, Object> emailTemplateContext) {
this.emailTemplateContext = emailTemplateContext;
this.recipients = recipients;
this.templateName = templateName;
this.subjectName = subjectName;
this.ccRecipients = ccRecipients;
}
public static class EmailBuilder {
// your builder code, just build the Email
}
}
And then have a service to send emails.
#Service
public class EmailServiceImpl implements NotificationService {
public void sendEmail(Email email) {
}
}
Call it like this
emailService.sendEmail(new Email.EmailBuilder().build());
There is no need to create a #service from the Email and EmailBuilder.
Related
I have a nested java map like this
inputMap: {jobId={EndpointReference={ReferenceParameters={ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job, SelectorSet={Selector=[JID_502260561923, root/im]}}, Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous}}, returncode=4096, messageId=null, arguments=null, message=null}
which I want to map to java pojo and here is my pojo classes.
#Getter
#Setter
#ToString
public class DMResponseMapper {
#Getter
#Setter
#ToString
public static class GetSysConfigDMResponseMapper {
#JsonProperty("jobId")
private EndpointReferenceMapper endpointReferenceMapper;
private Integer returnCode;
private String messageId;
private String arguments;
private String message;
#Getter
#Setter
#ToString
public static class EndpointReferenceMapper {
#JsonProperty("ReferenceParameters")
private ReferenceParametersMapper referenceParametersMapper;
#JsonProperty("Address")
private String address;
#Getter
#Setter
#ToString
public static class ReferenceParametersMapper {
#JsonProperty("ResourceURI")
private String resourceURI;
#JsonProperty("SelectorSet")
private SelectorSetMapper selectorSetMapper;
#Getter
#Setter
#ToString
public static class SelectorSetMapper {
#JsonProperty("Selector")
private List<String> selector;
}
}
}
}
}
but objectMapper.convertValue(inputMap, GetSysConfigDMResponseMapper.class) is NOT mapping the nested classes.. just the top level fields.
My objectMapper is instantiated like this:
static {
objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
Response Object is :
DMResponseMapper.GetSysConfigDMResponseMapper(endpointReferenceMapper=DMResponseMapper.GetSysConfigDMResponseMapper.EndpointReferenceMapper(referenceParametersMapper=null, address=null), returnCode=4096, messageId=null, arguments=null, message=null)
Can anyone please suggest, what is wrong here?
Upon debugging this is what I see:
Converted endpointReferenceMapper to type Object.
DMResponseMapper.GetSysConfigDMResponseMapper(endpointReferenceMapper={EndpointReference={ReferenceParameters={ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job, SelectorSet={Selector=[JID_502318722705, root/dcim]}}, Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous}}, returnCode=4096, messageId=null, arguments=null, message=null)
The DMResponseMapper pojo needs to follow the structure of your source data more closely.
Your source Map object has the following structure, based on the info in the question:
inputMap:
{
jobId={
EndpointReference={
ReferenceParameters={
ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job,
SelectorSet={
Selector=[JID_502260561923, root/im]
}
},
Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
}
},
returncode=4096,
messageId=null,
arguments=null,
message=null
}
So, I adapted your DMResponseMapper pojo class to more closely map to that structure - and I changed the nested class names as well. Here is a summary of the nested classes with their fields for your data:
//
// NOT the actual class - just an overview of the structure!
//
class DMResponseMapper {
private JobId jobId;
private Integer returncode;
private Object messageId;
private Object arguments;
private Object message;
class JobId {
private EndpointReference endpointReference;
class EndpointReference {
private ReferenceParameters referenceParameters;
private String address;
class ReferenceParameters {
private String resourceURI;
private SelectorSet selectorSet;
class SelectorSet {
private List<String> selector = null;
}
}
}
}
}
This gave me the following, when fleshed out with annotations and getters/setters:
//
// Here is the actual class, based on the above structure.
//
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class DMResponseMapper {
#JsonProperty("jobId")
private JobId jobId;
#JsonProperty("returncode")
private Integer returncode;
#JsonProperty("messageId")
private Object messageId;
#JsonProperty("arguments")
private Object arguments;
#JsonProperty("message")
private Object message;
#JsonProperty("jobId")
public JobId getJobId() {
return jobId;
}
#JsonProperty("jobId")
public void setJobId(JobId jobId) {
this.jobId = jobId;
}
#JsonProperty("returncode")
public Integer getReturncode() {
return returncode;
}
#JsonProperty("returncode")
public void setReturncode(Integer returncode) {
this.returncode = returncode;
}
#JsonProperty("messageId")
public Object getMessageId() {
return messageId;
}
#JsonProperty("messageId")
public void setMessageId(Object messageId) {
this.messageId = messageId;
}
#JsonProperty("arguments")
public Object getArguments() {
return arguments;
}
#JsonProperty("arguments")
public void setArguments(Object arguments) {
this.arguments = arguments;
}
#JsonProperty("message")
public Object getMessage() {
return message;
}
#JsonProperty("message")
public void setMessage(Object message) {
this.message = message;
}
public static class JobId {
#JsonProperty("EndpointReference")
private EndpointReference endpointReference;
#JsonProperty("EndpointReference")
public EndpointReference getEndpointReference() {
return endpointReference;
}
#JsonProperty("EndpointReference")
public void setEndpointReference(EndpointReference endpointReference) {
this.endpointReference = endpointReference;
}
public static class EndpointReference {
#JsonProperty("ReferenceParameters")
private ReferenceParameters referenceParameters;
#JsonProperty("Address")
private String address;
#JsonProperty("ReferenceParameters")
public ReferenceParameters getReferenceParameters() {
return referenceParameters;
}
#JsonProperty("ReferenceParameters")
public void setReferenceParameters(ReferenceParameters referenceParameters) {
this.referenceParameters = referenceParameters;
}
#JsonProperty("Address")
public String getAddress() {
return address;
}
#JsonProperty("Address")
public void setAddress(String address) {
this.address = address;
}
public static class ReferenceParameters {
#JsonProperty("ResourceURI")
private String resourceURI;
#JsonProperty("SelectorSet")
private SelectorSet selectorSet;
#JsonProperty("ResourceURI")
public String getResourceURI() {
return resourceURI;
}
#JsonProperty("ResourceURI")
public void setResourceURI(String resourceURI) {
this.resourceURI = resourceURI;
}
#JsonProperty("SelectorSet")
public SelectorSet getSelectorSet() {
return selectorSet;
}
#JsonProperty("SelectorSet")
public void setSelectorSet(SelectorSet selectorSet) {
this.selectorSet = selectorSet;
}
public static class SelectorSet {
#JsonProperty("Selector")
private List<String> selector = null;
#JsonProperty("Selector")
public List<String> getSelector() {
return selector;
}
#JsonProperty("Selector")
public void setSelector(List<String> selector) {
this.selector = selector;
}
}
}
}
}
}
This is invoked as follows:
First, some test data:
List<String> selector = new ArrayList();
selector.add("JID_502260561923");
selector.add("root/im");
Map<String, Object> selectorSet = new HashMap();
selectorSet.put("Selector", selector);
String resourceURI = "http://schemas.com/wbem/wscim/1/cim-schema/2/Job";
Map<String, Object> referenceParameters = new HashMap();
referenceParameters.put("ResourceURI", resourceURI);
referenceParameters.put("SelectorSet", selectorSet);
String address = "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous";
Map<String, Object> endpointReference = new HashMap();
endpointReference.put("ReferenceParameters", referenceParameters);
endpointReference.put("Address", address);
Map<String, Object> jobId = new HashMap();
jobId.put("EndpointReference", endpointReference);
Map<String, Object> inputMap = new HashMap();
inputMap.put("jobId", jobId);
inputMap.put("returncode", 4096);
inputMap.put("messageId", "foo");
inputMap.put("arguments", "bar");
inputMap.put("message", "baz");
Note I replaced your null values with strings, for testing and demonstration.
Then the code to perform the mapping:
ObjectMapper objectMapper = new ObjectMapper();
DMResponseMapper mapper = objectMapper.convertValue(inputMap, DMResponseMapper.class);
The resulting mapper object contains the test data:
I am implementing the OAUTH2 authentication server, with JWT.
If I use inMemory () token I get access normally.
However, if I use jdbc (dataSource) it always returns error 401. Could anyone help?
My AuthorizationServerConfigurerAdapter
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
.tokenEnhancer(jwtAccessTokenConverter()).userDetailsService(userDetailsService)
.requestFactory(customOauth2RequestFactory.requestFactory());
}
My tokenStore
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
And my jwtAccessTokenConverter
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
var converteToken = new CustomToken();
converteToken.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "password".toCharArray())
.getKeyPair("jwt"));
return converteToken;
}
And my CustomToken extends extends JwtAccessTokenConverter
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
var user = userRepository.findByLogin(authentication.getName());
Map<String, Object> additionalInformation = new HashMap<>() {{
put("idFuncionario", usuario.getIdFuncionario());
put("idEmpresa", usuario.getIdEmpresa());
put("perfis", usuario.descricaoPerfil());
put("login", usuario.getLogin());
}};
var defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(accessToken);
defaultOAuth2AccessToken.setAdditionalInformation(additionalInformation);
return super.enhance(defaultOAuth2AccessToken, authentication);
}
Now, my JDBC in AuthorizationServerConfigurerAdapter
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).passwordEncoder(passwordEncoder());
}
And my CustomFactory extends DefaultOAuth2RequestFactory
#Override
public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) {
if (requestParameters.get("grant_type").equals("refresh_token")) {
var authentication = tokenStore.readAuthenticationForRefreshToken(tokenStore.readRefreshToken(requestParameters
.get("refresh_token")));
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
authentication.getName(), null, userDetailsService.loadUserByUsername(authentication.getName())
.getAuthorities()));
}
return super.createTokenRequest(requestParameters, authenticatedClient);
}
And my JWT ENtity
#Entity
#Table(name = "oauth_client_details")
public class OAuthClientDetails extends AbstractEntity {
private String clientId;
private String clientSecret;
private String resourceIds;
private String scope;
private String authorizedGrantTypes;
private String webServerRedirectUri;
private String authorities;
private Integer accessTokenValidity;
private Integer refreshTokenValidity;
private String additionalInformation;
private String autoapprove;
contructor / geters / seters
My solution:
I created this method in a class that extends AuthorizationServerConfigurerAdapter
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientService);
}
And this service:
#Service
public class ClientService implements ClientDetailsService {
#Autowired
private ClienteRepository clienteRepository;
#Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
var cliente = clienteRepository.findById(clientId);
var clienteDetail = new ClienteDetalhes(
cliente.get().getClientId(),
String.join(",", cliente.get().getResourceIds()),
cliente.get().getClientSecret(),
String.join(",", cliente.get().getScope()),
String.join(",", cliente.get().getAuthorizedGrantTypes()),
String.join(",", cliente.get().getRegisteredRedirectUri()),
null,
cliente.get().getAccessTokenValiditySeconds(),
cliente.get().getRefreshTokenValiditySeconds(),
cliente.get().getAutoApproveScope(),
null);
return clienteDetail;
}
}
And this Entity
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(schema = "cliente")
public class ClienteDetalhes implements Serializable, ClientDetails {
private static final ObjectMapper mapper = new ObjectMapper();
#Id
#Column(nullable = false, unique = true)
private String clientId;
private String resourceIds;
private String clientSecret;
private String scope;
#Column(nullable = false)
private String authorizedGrantTypes;
private String registeredRedirectUri;
private String authorities;
#Column(nullable = false)
private Integer accessTokenValiditySeconds;
#Column(nullable = false)
private Integer refreshTokenValiditySeconds;
private String autoApproveScope;
private String additionalInformation;
#Override
public boolean isSecretRequired() {
return !StringUtils.isEmpty(this.clientSecret);
}
#Override
public boolean isAutoApprove(String scope) {
return false;
}
#Override
public Map<String, Object> getAdditionalInformation() {
try {
return mapper.readValue(this.additionalInformation, Map.class);
} catch (IOException e) {
return new HashMap<>();
}
}
#Override
public Collection<GrantedAuthority> getAuthorities() {
Set<String> set = StringUtils.commaDelimitedListToSet(this.authorities);
Set<GrantedAuthority> result = new HashSet<>();
set.forEach(authority -> result.add(new GrantedAuthority() {
#Override
public String getAuthority() {
return authority;
}
}));
return result;
}
#Override
public Set<String> getRegisteredRedirectUri() {
return StringUtils.commaDelimitedListToSet(this.registeredRedirectUri);
}
#Override
public Set<String> getAuthorizedGrantTypes() {
return StringUtils.commaDelimitedListToSet(this.authorizedGrantTypes);
}
#Override
public boolean isScoped() {
return this.getScope().size() > 0;
}
#Override
public Set<String> getScope() {
return StringUtils.commaDelimitedListToSet(this.scope);
}
#Override
public Set<String> getResourceIds() {
if (StringUtils.isEmpty(this.resourceIds)) {
return new HashSet<>();
} else {
return StringUtils.commaDelimitedListToSet(this.resourceIds);
}
}
}
Now all works ok!
I currently have an application that I'm attempting to diagnose what in the setup I've done incorrectly, and am not having any luck in determining why it's not working outside of very specific situations.
First the code that I'm using.
Configuration.java
#EnableBatchProcessing
#SpringBootApplication(scanBasePackages="com.lcbo")
#EnableIntegration
public class COnfig {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Autowired
private LCBOInventoryTrackerProperties inventoryTrackerProperties;
#Bean
public Job processLCBOInventory(#Qualifier("getLCBOStoreDataStep") final Step getLCBOStoreDataStep) {
return jobBuilderFactory
.get("processLCBOInventory")
.incrementer(new RunIdIncrementer())
.start(getLCBOStoreDataStep)
.build();
}
/**
* This tasklet downloads the .zip file, unzips, and saves it in the appropriate folder under resources.
* Execute at 6am daily
*
// * #param AcquireDataFileTasklet acquireDataFiles
* #return Step - returns Step status; either SUCCESS or FAILURE
*/
#Bean
public Step getCurrentLCBODataStep(final AcquireDataFileTasklet acquireDataFiles,
final ExecutionContextPromotionListener listener) {
return stepBuilderFactory
.get("getCurrentLCBODataStep")
.tasklet(acquireDataFiles)
.allowStartIfComplete(true)
.listener(listener)
.build();
}
#Bean
public Step getLCBOStoreDataStep(final LCBOStoreReader lcboStoreReader,
final LCBOStoreWriter lcboStoreWriter) {
return stepBuilderFactory
.get("getLCBOStoreDataStep")
.<LCBOStore, LCBOStore>chunk(inventoryTrackerProperties.getDefaults().getChunkSize())
.reader(lcboStoreReader)
.writer(lcboStoreWriter)
.build();
}
}
The reader class
#Component
public class LCBOStoreReader extends AbstractLCBOReader implements ItemReader, InterstepDataRetriever {
private static final Logger log = LoggerFactory.getLogger(LCBOStoreReader.class);
#Override
public ItemReader<LCBOStore> read() throws UnexpectedInputException, ParseException, NonTransientResourceException {
Class<LCBOStore> classType = LCBOStore.class;
return createCSVReader(classType, currentCSVFilePath, inventoryTrackerProperties.getLCBOFilPropertiess().getStores());
}
/*
#Override
public void beforeStep(final StepExecution stepExecution) {
JobExecution jobExecution = stepExecution.getJobExecution();
ExecutionContext jobContext = jobExecution.getExecutionContext();
this.currentWorkingDate = (String) jobContext.get("currentWorkingDateKey");
}
*/
#Override
public void retrieveInterstepDataFromJobContext(final ExecutionContext jobContext) {
this.currentCSVFilePath = (String) jobContext.get("currentCSVFilePathKey");
}
}
and the class it extends (because the FlatFileItemReader setup is used by multiple readers)
public abstract class AbstractLCBOReader {
#Autowired
protected LCBOInventoryTrackerProperties inventoryTrackerProperties;
protected String currentCSVFilePathKey;
protected String currentCSVFilePath;
private static final Logger log = LoggerFactory.getLogger(AbstractLCBOReader.class);
protected <T> ItemReader<T> createCSVReader(final Class<T> classType,
final String currentCSVFilePath,
final LCBOFileDetailsProperties properties) {
FlatFileItemReader<T> reader = new FlatFileItemReader<>();
// Skip a line to ignore the header information in these files
reader.setLinesToSkip(properties.getNumberOfLinesToSkipInFile());
reader.setResource(new FileSystemResource(currentCSVFilePath + File.separator + properties.getFileName()));
reader.setLineMapper(createLineMapper(classType, properties));
reader.setRecordSeparatorPolicy(new DefaultRecordSeparatorPolicy());
reader.setEncoding("utf8");
return reader;
}
private <T> LineMapper<T> createLineMapper(final Class<T> classType, final LCBOFileProperties.LCBOFileDetailsProperties properties) {
DefaultLineMapper<T> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(createLineTokenizer(properties));
lineMapper.setFieldSetMapper(createFieldSetMapper(classType));
return lineMapper;
}
private <T> FieldSetMapper<T> createFieldSetMapper(final Class<T> classType) {
BeanWrapperFieldSetMapper<T> fieldSetMapper = new BeanWrapperFieldSetMapper<>();
fieldSetMapper.setTargetType(classType);
return fieldSetMapper;
}
private LineTokenizer createLineTokenizer(final LCBOFileProperties.LCBOFileDetailsProperties properties) {
LCBOFileProperties.Column[] columns = properties.getColumns();
int[] columnIndexes = new int[columns.length];
String[] columnNames = new String[columns.length];
// populating the columnIndexes
for (int i = 0; i < columns.length; i++) {
columnIndexes[i] = columns[i].getColumnIndex();
columnNames[i] = columns[i].getColumnName();
}
DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
lineTokenizer.setIncludedFields(columnIndexes);
lineTokenizer.setNames(columnNames);
lineTokenizer.setDelimiter(",");
lineTokenizer.setQuoteCharacter('"');
return lineTokenizer;
}
}
The error when executing this will be that the object cannot be cast from FlatFileItemreader to the object passed as the first parameter in createCSVReader. Here's an example.
public class LCBOStore {
private Long id;
private String addressLineOne;
private String addressLineTwo;
private String city;
private String postalCode;
private String latitude;
private String longitude;
private String updatedAt; //Convert to Date
public LCBOStore(final Long id, final String addressLineOne, final String addressLineTwo, final String city,
final String postalCode, final String latitude, final String longitude, final String updatedAt) {
this.id = id;
this.addressLineOne = addressLineOne;
this.addressLineTwo = addressLineTwo;
this.city = city;
this.postalCode = postalCode;
this.latitude = latitude;
this.longitude = longitude;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
public String getAddressLineOne() {
return addressLineOne;
}
public String getAddressLineTwo() {
return addressLineTwo;
}
public String getCity() {
return city;
}
public String getPostalCode() {
return postalCode;
}
public String getLatitude() {
return latitude;
}
public String getLongitude() {
return longitude;
}
public String getUpdatedAt() {
return updatedAt;
}
public void setId(final Long id) {
this.id = id;
}
public void setAddressLineOne(final String addressLineOne) {
this.addressLineOne = addressLineOne;
}
public void setAddressLineTwo(final String addressLineTwo) {
this.addressLineTwo = addressLineTwo;
}
public void setCity(final String city) {
this.city = city;
}
public void setPostalCode(final String postalCode) {
this.postalCode = postalCode;
}
public void setLatitude(final String latitude) {
this.latitude = latitude;
}
public void setLongitude(final String longitude) {
this.longitude = longitude;
}
public void setUpdatedAt(final String updatedAt) {
this.updatedAt = updatedAt;
}
#Override
public String toString() {
return "StoreDBModel [id=" + id + ", addressLineOne=" + addressLineOne + ", city=" + city
+ ", postalCode=" + postalCode + ", latitude=" + latitude + ", longitude="
+ longitude + ", updatedAt=" + updatedAt + "]";
}
}
Now if I move the FlatFileItemReader mode that exists in createCSVReader into the constructor of the custom Reader class, or have it so it's in the configuration file, it works fine. However, I couldn't figure out how to work with job and step context in those configurations (the constructor executes before you can access step and jobContext it seems from my testing, and I could never figure how to access when put in the Config class.). Plus to me at least, it looks cleaner to have the Reader code in it's own file not being stuffed in the constructor.
So in a nutshell, is there a way to fix this os that having it in it's own reader class would work? Am I doing this incorrectly and using bad practices? Maybe a mix of the two? If there's anything missing please ask away and I'll attempt to clarify.
So I found the answer to be very simple with some help from those in the comments. Here's my solution.
First, add the bolded code to the abstract class createCSVWriter method
**protected <T> T** createCSVReader(final Class<T> classType,
final String currentCSVFilePath,
final LCBOFileDetailsProperties properties) throws Exception {
FlatFileItemReader<T> reader = new FlatFileItemReader<>();
// Skip a line to ignore the header information in these files
reader.setLinesToSkip(properties.getNumberOfLinesToSkipInFile());
reader.setResource(new FileSystemResource(currentCSVFilePath + File.separator + properties.getFileName()));
reader.setLineMapper(createLineMapper(classType, properties));
reader.setRecordSeparatorPolicy(new DefaultRecordSeparatorPolicy());
reader.setEncoding("utf8");
**return reader.read();**
}
Doing the read call manually will prevent it returning more then needed for your reader class. Then in the reader class edit the following
#Override
public **LCBOStore** read() throws **Exception**, UnexpectedInputException, ParseException, NonTransientResourceException {
Class<LCBOStore> classType = LCBOStore.class;
return createCSVReader(classType, currentCSVFilePath, inventoryTrackerProperties.getLCBOFilPropertiess().getStores());
}
This just returns the object you've created and hence issue resolved.
We are using Spring Boot to expose a REST endpoint which is called by a dumb client which delivers us the following:
{
"timestamp": "2016-08-16T14:30.000Z",
"data": "{\"amount\":1,\"product\":\"BASIC PRODUCT\"}"
}
We've created the following objects:
#JsonDeserialize(builder = Message.Builder.class)
public final class Message {
private final String timestamp;
private final Data data;
public String getTimestamp() {...}
public Data getData() {...}
#JsonPOJOBuilder
public static final class Builder {
private String timestamp;
private Data data;
public Builder withTimestamp(final String timestamp) {...}
public Builder withData(final Data data) {...}
}
}
and
#JsonDeserialize(builder = Data.Builder.class)
public final class Data {
private final String product;
private final int amount;
public String getProduct() {...}
public int getAmount() {...}
#JsonPOJOBuilder
public static final class Builder {
private String product;
private int amount;
public Builder withProduct(final String product) {...}
public Builder withAmount(final int amount) {...}
}
}
and exposed the endpoint as
#RequestMapping(consumes = "application/json", method = POST)
public ResponseEntity<?> receive(#RequestBody Message message) {
/// ...
}
but control doesn't even reach the receive method and fails with 400 BAD REQUEST. I believe this has to do with the fact that data is a JSON-valued string. Does Jackson provide any annotation that I can use to force the JSON-valued string to be deserialized as an instance of Data?
The key is in public Builder withData() method of Message.Builder.class to explicitly parse JSON-valued string to Data type. Change the method parameter to String instead of Data and call ObjectMapper().readValue(JSON-valued string, Data.class) to deserialize it into Data.
For example like this:
public Builder withData(final String jsonValue) throws JsonParseException, JsonMappingException, IOException {
Data data = new ObjectMapper().readValue(jsonValue, Data.class);
this.data = data;
return this;
}
For the clarity sake here you are my whole POJOs:
Message:
public final class Message {
private final String timestamp;
private final Data data;
private Message(Builder builder){
this.timestamp = builder.timestamp;
this.data = builder.data;
}
public String getTimestamp() {...}
public Data getData() {...}
#JsonPOJOBuilder
public static final class Builder {
private String timestamp;
private Data data;
private static ObjectMapper mapper = new ObjectMapper();
public Builder withTimestamp(final String timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder withData(final String jsonValue) throws JsonParseException, JsonMappingException, IOException {
Data data = mapper.readValue(jsonValue, Data.class);
this.data = data;
return this;
}
public Message build() {
return new Message(this);
}
} // Builder
}
Data:
public final class Data {
private final String product;
private final int amount;
private Data(Builder builder){
this.product = builder.product;
this.amount = builder.amount;
}
public String getProduct() {...}
public int getAmount() {...}
#JsonPOJOBuilder
public static final class Builder {
private String product;
private int amount;
public Builder withProduct(final String product) {
this.product = product;
return this;
}
public Builder withAmount(final int amount) {
this.amount = amount;
return this;
}
public Data build() {
return new Data(this);
}
} // Builder
}
Hope it helps.
I need to create a Map from java bean such that the key is prefixed with name of the java bean variable. I am using jackson for this. Example given below:
public class Address{
String city;
String state;
//setters and getters
}
Address address = new Address();
address.setCity("myCity");
address.setState("myState");
I am creating map using following:
ObjectMapper objectMapper = new ObjectMapper();
Map map = objectMapper.convertValue(address, HashMap.class);
Which gives me following output:
{"city":"myCity", "state":"myState"}
I need to add class variable name to the key as shown below:
{"address.city":"myCity", "address.state":"myState"}
How do I achieve that?
If you have jackson-annotations enabled:
public class Address{
#JsonProperty("address.city")
String city;
#JsonProperty("address.state")
String state;
//setters and getters
}
read more about it here: https://github.com/FasterXML/jackson-annotations
It is possible to customise bean serialization by registering a BeanSerializerModifier. This specifically supports renaming properties by applying a NameTransformer to each BeanPropertyWriter.
#Test
public void prepend_class_name_to_property_keys() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Function<Class<?>, String> classPrefix = clazz -> clazz.getSimpleName().toLowerCase() + ".";
mapper.registerModule(new Module() {
#Override
public String getModuleName() {
return "Example";
}
#Override
public Version version() {
return Version.unknownVersion();
}
#Override
public void setupModule(SetupContext context) {
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
String prefix = classPrefix.apply(beanDesc.getBeanClass());
return beanProperties.stream().map(prop -> prop.rename(new NameTransformer() {
#Override
public String transform(String name) {
return prefix + name;
}
#Override
public String reverse(String transformed) {
return transformed.substring(prefix.length());
}
})).collect(toList());
}
});
}
});
assertThat(mapper.writeValueAsString(new Address("somewhere", "someplace")),
equivalentTo("{ 'address.line1' : 'somewhere', 'address.line2' : 'someplace'}"));
}
public static final class Address {
public final String line1;
public final String line2;
public Address(String line1, String line2) {
this.line1 = line1;
this.line2 = line2;
}
}