I've searched a lot in this forum and other websites, but I'm still stuck with my problem.
I'm actually using modelmapper to convert an entity to a DTO.
Here is the Entity :
#Entity
public class Candidate implements Serializable {
#Id
#GeneratedValue (strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column (name = "name")
private String lastname;
#Column (name = "firstname")
private String firstname;
#Column (name = "phone")
private String phoneNumber;
#Column (name = "mail")
private String email;
#Column (name = "title")
private int title;
#OneToMany (mappedBy = "candidateId")
private Collection<Candidature> Interviews;
Here is Candidature Entity (that you find in the first Entity's collection):
public class Candidature implements Serializable {
#Id
#NotBlank
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn (name = "candidat_id")
private Candidate candidateId;
#Column(name = "interview")
#Temporal (TemporalType.DATE)
private Date dateInterview;
#Column(name ="status")
private String status;
And here is the DTO :
public class CandidateDTO {
private Long id;
private String lastname;
private String firstname;
private String phoneNumber;
private String email;
private String title;
private String dateLastInterview;
As you can see, there are some differences.
The problem I face is that the last attribute of DTO (dateLastInterview) comes from the Collection<Candidature> and more precisely it must be the last dateInterview converted into String.
Convert a Date into String is not a problem. Getting the last item of a Collection neither.
But I can't make it work with modelMapper.
Here is a sample code I tried :
modelMapper = new ModelMapper();
Converter<Candidate, CandidateDTO> converter = new Converter<Candidate, CandidateDTO>()
{
#Override
public CandidateDTO convert(MappingContext<Candidate, CandidateDTO> mappingContext) {
Candidate candidate = mappingContext.getSource();
CandidateDTO cdto = new CandidateDTO();
List<Candidature> list = (List) candidate.getInterviews();
Date date = list.get(list.size()-1).getDateInterview();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String dateInterviewConverted = df.format(date);
mappingContext.getDestination().setTitle(mappingContext.getSource().getTitle());
mappingContext.getDestination().setDateLastInterview(dateInterviewConverted);
return cdto;
}
};
modelMapper.createTypeMap(Candidate.class, CandidateDTO.class).setConverter(converter);
(and I tried, instead of the last line above : modelMapper.addConverter(converter); but same result)
But it doesn't work, I get all attributes at null.
I previously succeded using
map().setTitle(source.getTitle());
map().setDateLastInterview(dateInterviewConverted);
And then converting the Date to String in my DTO "set" method, but it seems that it shouldn't be here, but into the ModelMapper class or the class that is using it.
Do you have an idea ? I'm new with modelMapper, and I keep browsing google and I can't find (or maybe understand ?) any response that might help me.
Thanks
Ok I think I succeded.
Using the converter was the right thing, but I wasn't using it correctly. For the converter, the two objets that you put inside <> are the ones of the attributes concerned by the converter.
For example, for the first converter, I wanted to parameter the conversion of the Collection (coming from an object Candidate) to become a String (to match the attribute of the DTO).
So then you only have to create a PropertyMap with the Class and ClassDTO, and in the configure() method you only mention the attributes that will use special parameters (the other ones are correct since they respect the standard mapping).
Converter<Collection<Candidature>, String> convertLastDateToString = new Converter<Collection<Candidature>, String>() {
public String convert(MappingContext<Collection<Candidature>, String> context) {
List<Candidature> candidatureList = (List)context.getSource();
String dateInterviewConverted = "";
if (candidatureList.size() > 0) {
Date lastInterview = candidatureList.get(0).getDateInterview();
for (int i = 0; i < candidatureList.size(); i++) {
if (candidatureList.get(i).getDateInterview().after(lastInterview)) {
lastInterview = candidatureList.get(i).getDateInterview();
}
}
// converts the Date to String
DateFormat df = new SimpleDateFormat(DATE_FORMAT);
dateInterviewConverted = df.format(lastInterview);
}
return dateInterviewConverted;
}
};
// allows custom conversion for Title attribute
// the source (Candidate) has a title attribute in int type
// the destination (CandidateDTO) has a title attributes in String type
Converter<Integer, String> convertTitleToString = new Converter<Integer, String>(){
public String convert(MappingContext<Integer, String> context){
return Title.values()[context.getSource()].toString();
}
};
// define explicit mappings between source and destination properties
// does only concernes the attributes that will need custom mapping
PropertyMap<Candidate, CandidateDTO> candidateMapping = new PropertyMap<Candidate, CandidateDTO>()
{
protected void configure()
{
// to map these two attributes, they will use the corresponding converters
using(convertTitleToString).map(source.getTitle()).setTitle(null);
using(convertLastDateToString).map(source.getCandidatures()).setDateLastInterview(null);
}
};
// add the mapping settings to the ModelMapper
modelMapper.addMappings(candidateMapping);
Related
I'm writing a program that changes a member's password, I fetched the user by id from the database when I test the endpoint on postman it returns 200 OK, but fails to update the password in the database to the new password, What is the right logic to use for this task? my code is below.
Member
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name ="member",
indexes = {
#Index(
columnList = "email_address",
name = "email_address_idx",
unique = true
),
},
uniqueConstraints = {
#UniqueConstraint(
columnNames = {"email_address", "phone_number"},
name = "email_address_phone_number_uq"
)
}
)
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "nationality_id")
private Country nationality;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "country_of_residence_id")
private Country countryOfResidence;
#Temporal(TemporalType.DATE)
#Column(name ="date_of_birth")
private Date dateOfBirth = new Date();
#Column(name ="current_job_title")
private String currentJobTitle;
#Column(name = "email_address", nullable = false)
private String emailAddress;
#Column(name = "username")
private String username;
#Column(name ="phone_number")
private String phoneNumber;
#Column(name ="city")
private String city;
#Column(name ="state")
private String state;
#Column(name ="password", nullable = false)
private String password;
}
PasswordDto
#Data
public class ChangePasswordDto {
private String password;
private String oldPassword;
private String newPassword;
private String reNewPassword;
PasswordService
#Slf4j
#Service
public class ChangePasswordServiceImpl implements ChangePasswordService {
#Autowired
private ModelMapper modelMapper;
#Autowired
private PasswordEncoder passwordEncoder;
private final PasswordJpaRepository jpaRepository;
public ChangePasswordServiceImpl(PasswordJpaRepository jpaRepository) {
this.jpaRepository = jpaRepository;
}
#Override
#Transactional
public Member changePassword(Long id, ChangePasswordDto password) {
final Member member = jpaRepository.findById(id);
Member getPassword = new Member();
getPassword = modelMapper.map(id, Member.class);
Member updatedPassword = new Member();
if (member.getPassword().equals(checkIfValidOldPassword(member, password.getOldPassword()))){
if (password.getNewPassword().equals(password.getReNewPassword())) {
updatedPassword = changPassword(member, password.getNewPassword());
}
}else{
return null;
}
return updatedPassword;
}
#Override
#Transactional
public boolean checkIfValidOldPassword(Member member, String oldPassword) {
return matches(oldPassword, member.getPassword());
}
#Override
#Transactional
public Member changPassword(Member member, String password) {
member.setPassword(password);
jpaRepository.save(member);
return member;
}
}
PasswordController
#RestController
#RequestMapping(
value = "password",
produces = { MediaType.APPLICATION_JSON_VALUE }
)
public class ChangePasswordController {
private ChangePasswordService service;
public ChangePasswordController(ChangePasswordService passwordService) {
this.service = passwordService;
}
#PostMapping("/change-password/{id}")
public Member changePassword(#Validated #RequestBody ChangePasswordDto password, #PathVariable(name = "id") Long id){
return service.changePassword(id, password);
}
}
Troubleshooting and Debugging
In the future, it would be helpful for you to post the request as a cURL command as well as the Catalina logs.
Your bug is in the following statement
if (member.getPassword().equals(checkIfValidOldPassword(member, password.getOldPassword()))){
// The above expression is always evaluating false
}
The member.getPassword() accessory method returns a String. However checkIfValidOldPassword method returns a boolean. Let's refactor the code for demonstration.
String pwd = member.getPassword();
String opwd = password.getOldPassword();
boolean isValud = checkIfValidOldPassword(member, opwd);
assert pwd.equals(isValid);
You are attempting to evaluate the equality of a String and a primitive boolean ( autoboxed to the Boolean wrapper class object ). Likely this statement always evaluates false thus you are returning null and not invoking the code that actually makes the update.
Autoboxing Explained
The reason this did not throw a compile time exception is due to a feature known as Autoboxing. Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes.
In your example, the equals method has a single parameter of type Object. So although you passed a primitive boolean as the first parameter in the equals method, the Java compiler converted it to an Object of type Boolean. Because Boolean is an object, and all objects inherit from Object, no exception is thrown.
Most likely you are comparing the response of ‘toString’ method on your Boolean object which returns the string “true” when the primitive boolean value corresponds with true and “false” otherwise.
Security Concerns
Please be extremely careful when you are attempting to roll your own authentication or authorization features. For the most part, a password should be salted and encrypted before storing the information at-rest. Therefore, you should only ever be able to compare one salted/encrypted string with another salted/encrypted string
I need to aggregate some data using java and Mongodb.
So my JS script it's that:
db.post.aggregate([
{$match: {date: {$gte: ISODate("2019-08-28T17:50:09.803Z"), $lte: ISODate("2019-12-03T21:45:51.412+00:00")}}},
{$project: {author: 1, _id: 0}},
{$group: {_id: "$author", count: {$sum:1}}}])
And my Java Code using spring+java is:
Aggregation aggregation =
newAggregation(
match(Criteria.where("date")
.lte(dynamicQuery.getEndDate())
.gte(dynamicQuery.getInitDate())),
project("author").andExclude("_id"),
group("author")
.count()
.as("total"));
AggregationResults<Post> result = mongoTemplate.aggregate(aggregation,Post.class, PostSummary.class);
List<Post> map = result.getMappedResults();
And I need to aggregate the sum of documents by authorId. My code returns the user code and no sum of documents.
And we have 2 Collections:
#EqualsAndHashCode(of = "id")
//TODO: Change the #Data to the properly scenario, we don't need
setters in this class anymore.
#Data
#Document (collection = "user")
//TODO: Change collection name to post, because collection are a group
of elements
public class User {
#Id
private String id;
private String name;
private String username;
#Email
private String email;
private String imageUrl;
private String providerId;
private LocalDateTime firstAccess;
}
Post Document:
#Data
#Document(collection = "post")
//TODO: Change collection name to post, because collection are a group of elements
public class Post {
public static final String AUTHOR_FIELD_NAME = "author";
public static final String DATE_FIELD_NAME = "date";
#Id
private String id;
private String content;
#DBRef(lazy = true)
#Field(AUTHOR_FIELD_NAME)
private User author;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
#Field(DATE_FIELD_NAME)
private LocalDateTime date;
public Post(String content, User author, LocalDateTime date) {
this.content = content;
this.author = author;
this.date = date;
}
}
I have one solution, but I do not know if it is a better solution, so, let's go.
Because the Entity represented inside the Post Domain is one DBRef, the MongoDB does not parse the entire Stringo to the PostSummary.Class
#Getter
#Setter
public class PostSummary {
private User author;
private int count;
}
So, for the solution, I only parse the #Id in the author, and it's working. So If anyone has a better solution we have one solution now.
And should be like this:
#Getter
#Setter
public class PostSummary {
#Id
private User author;
private int count;
}
I'm trying to fetch just a part of the model using Ebean in Play! Framework, but I'm having some problems and I didn't found any solutions.
I have these models:
User:
#Entity
#Table(name = "users")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User extends Model{
#Id
private int id;
#NotNull
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name")
private String lastName;
#NotNull
#Column(nullable = false)
private String username;
#NotNull
#Column(nullable = false)
private String email;
private String gender;
private String locale;
private Date birthday;
private String bio;
#NotNull
#Column(nullable = false)
private boolean active;
private String avatar;
#Column(name = "created_at",nullable = false)
private Date createdAt;
#OneToMany
private List<UserToken> userTokens;
// Getters and Setters omitted for brevity
}
UserToken:
#Entity
#Table(name = "user_tokens")
public class UserToken extends Model {
#Id
private int id;
#Column(name = "user_id")
private int userId;
private String token;
#Column(name = "created_at")
#CreatedTimestamp
private Date createdAt;
#ManyToOne
private User user;
// Getters and Setters omitted for brevity
}
And then, I have a controller UserController:
public class UserController extends Controller{
public static Result list(){
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
return Results.ok(Json.toJson(user));
}
}
I expected that, when using the .select(), it would filter the fields and load a partial object, but it loads it entirely.
In the logs, there is more problems that I don't know why its happening.
It is making 3 queries. First is the one that I want. And then it makes one to fetch the whole Model, and another one to find the UserTokens. I don't know why it is doing these last two queries and I wanted just the first one to be executed.
Solution Edit
After already accepted the fact that I would have to build the Json as suggested by #biesior , I found (out of nowhere) the solution!
public static Result list() throws JsonProcessingException {
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
JsonContext jc = Ebean.createJsonContext();
return Results.ok(jc.toJsonString(user));
}
I render only the wanted fields selected in .select() after using JsonContext.
That's simple, when you using select("...") it always gets just id field (cannot be avoided - it's required for mapping) + desired fields, but if later you are trying to access the field that wasn't available in first select("...") - Ebean repeats the query and maps whole object.
In other words, you are accessing somewhere the field that wasn't available in first query, analyze your controller and/or templates, find all fields and add it to your select (even if i.e. they're commented with common HTML comment in the view!)
In the last version of Play Framework (2.6) the proper way to do this is:
public Result list() {
JsonContext json = ebeanServer.json();
List<MyClass> orders= ebeanServer.find(MyClass.class).select("id,property1,property2").findList();
return ok(json.toJson(orders));
}
I need information how to store the best way a Document (Java POJO) with the Spring-Data-Elasticsearch #Document Annotation which includes a Map
#Document(indexName = "downloadclienterrors", type = "downloadclienterror")
public class DownloadClientErrorLogElasticsearch {
#Id
private Long id;
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private String host;
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private String shortMessage;
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private String fullMessage;
#Field(type = FieldType.Date)
private String clientTimestamp;
private Integer level;
private Map<String, String> additionalFieldList;
...
}
Like the POJO is created in this 1st class I can store it via my repository in the elastic search instance.
This is the way how I add then data to it, I wanna be flexible which JSON fields I add, because that's flexible from my client software.
additionalFieldList.put("url", "http://www.google.de");
additionalFieldList.put("user_agent", "Browser/1.0.0 Windows");
My problem is that I need also the fields in the additionalFieldList marked as .not_analyzed. (f.e additionalFieldList.url, additionalFieldList.user_agent).
I would like to have the same behaviour like with the FieldIndex.not_analyzed annotation on a String also on my Map but of course only for the value in the map.
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private Map<String, String> additionalFieldList;
But that doesn't work when I try to store the document. I receive a ugly Exception.
When someone knows a way, or how it would be better to design such a document in elasticsearch, because I am quit fresh and new in this area I would love to hear some comments.
Thanks before and grey greetings from Hamburg,
Tommy Ziegler
You can use #Mapping annotation to configure dynamic_templates.
Just put your mapping file in your classpath and annotate your POJO with #Mapping
Mapping example
JSON
{
"downloadclienterrors": {
"dynamic_templates": [
{
"additionalFieldList": {
"path_match": "additionalFieldList.*",
"mapping": {
"type": "string",
"index": "not_analyzed"
}
}
}
]
...
}
}
POJO
#Mapping(mappingPath = "/downloadclienterrors.json")
#Document(indexName = "downloadclienterrors", type = "downloadclienterror")
public class DownloadClientErrorLogElasticsearch {
...
}
What you have to do is to create a another class additional and add additionalFieldList there.
something like this-
public class additional {
private Map<String, String> additionalFieldList;
}
and then use this class in your pojo
#Document(indexName = "downloadclienterrors", type = "downloadclienterror")
public class DownloadClientErrorLogElasticsearch {
#Id
private Long id;
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private String host;
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private String shortMessage;
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private String fullMessage;
#Field(type = FieldType.Date)
private String clientTimestamp;
private Integer level;
#Field(type = FieldType.Nested)
private additional additional;
...
}
I am working on a Play 2.2 Form application. I create a form using this function
public static Result editIndicator() {
Indicators addIndObj = new Indicators();
addIndObj.aDt = (new Date()).toString();
addIndObj.aUid = Long.parseLong(play.mvc.Controller.session("userid"));
addIndObj.dTag = "N";
// List of all Countries
ALLCommon table_listobj = Indicators.ddl1();
Form<Indicators> newIndicatorForm = form(Indicators.class).fill(
addIndObj);
return (ok(addindicator.render(newIndicatorForm, table_listobj)));
}
the Indicator Model has parameters with constraints #Required for the parameters as follows
#Constraints.Required
#Column(name = "A_DT")
public String aDt;
#Constraints.Required
#Column(name = "A_UID")
public Long aUid;
#Constraints.Required
#Column(name = "D_TAG")
public String dTag;
#Column(name = "TIME")
#Required
#Formats.DateTime(pattern = "HH:mm:ss")
public Date time;
#Constraints.Required
#Column(name = "INDFREQUENCY")
public String indFrequency;
So, I set the values before and then bind it to form. I don't use all of these #Required values (just the Frequency part) in my form and when I try to get the filledform I get form errors
Form(of=class models.Indicators,
data={}, value=None,
errors={time=[ValidationError(time,error.required,[])],
aDt=[ValidationError(aDt,error.required,[])],
dTag=[ValidationError(dTag,error.required,[])],
aUid=[ValidationError(aUid,error.required,[])],
indFrequency=[ValidationError(indFrequency,error.required,[])]})
Do I need to set these values in form even if I don't use it? Or I am missing something..
Any help is appriciated.. thanks in advance.. :)
Found the answer..
1) You have to use all parameters that are required for your model i.e #Required and if you don't have to use it in your form. Simply put it in a <div display:none> tag.
2) On Submit button we call the #forms(routes.Application.index) but, the brackets should encapsulate your complete code and not just around the submit button. Hence,
the best practice would be #forms(routes.Application.index){ your complete code here}.
You can use groups if you don't need to validate all fields all the time.
For example:
// interfaces to create groups
public interface Step1 {}
public interface Step2 {}
So you need to add thats groups to your fields:
#Required(groups = {Step1.class})
#Column(name = "A_DT")
public String aDt;
#Required(groups = {Step1.class})
#Column(name = "A_UID")
public Long aUid;
#Required(groups = {Step1.class})
#Column(name = "D_TAG")
public String dTag;
#Column(name = "TIME")
#Required(groups = {Step2.class})
#Formats.DateTime(pattern = "HH:mm:ss")
public Date time;
#Required(groups = {Step2.class})
#Column(name = "INDFREQUENCY")
public String indFrequency;
Then:
// this only validates the fields that have Step1 group (aDt, aUid, dTag in this example)
Form<Indicators> newIndicatorForm = form(Indicators.class, Step1.class)
.fill(addIndObj);
// this only validates the fields that have Step2 group (time, indFrequency in this example)
Form<Indicators> newIndicatorForm = form(Indicators.class, Step2.class)
.fill(addIndObj);
// this validates the fields of both groups (all the fields in this example)
Form<Indicators> newIndicatorForm = form(Indicators.class, Step1.class, Step2.class)
.fill(addIndObj);