I have an app (Spring MVC + Hibernate) and AppFuse Framework too.
I have two entities: User and Robot with a Many-To-One relationship.
I need to add a drop down list to for owner (User) to the Robot form (robotForm.jsp).
The Robot entity has a User. I read that I must create a custom Editor for User. (UserCustomEditor extends PropertyEditorSupport) and override referenceData in the RobotFormController add in the initBinder too.
RobotFormController
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat(getText("date.format"));
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, null,
new CustomDateEditor(dateFormat, true));
binder.registerCustomEditor(Long.class, null,
new CustomNumberEditor(Long.class, null, true));
binder.registerCustomEditor(User.class, new UserEditor(userManager));
}
// ...
protected Map referenceData(HtppServletRequest request) throws Exception {
Map ownersMap = new HashMap();
ownersMap.put("owners", userManager.getUsers();
return ownersMap;
}
userManager.getUsers(); return a List of Users.
UserEditor (maybe here is my error).
public class UserEditor extends PropertyEditorSupport {
private final UserManager userManager;
protected final transient Log log = LogFactory.getLog(getClass());
public UserEditor(UserManager userManager) throws IllegalArgumentException {
this.userManager = userManager;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (text != null && text.length() > 0) {
try {
User user = userManager.getUser(new String (text));
super.setValue(user);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException();
}
} else {
super.setValue(null);
}
}
#Override
public String getAsText() {
User user = (User) super.getValue();
return (user != null ? (user.getId()+"").toString(): "");
}
}
robotForm.jsp
<form:select path="owner" itemValue="id" itemLabel="name" items="${owners}"
</form:select>
I get a NullPointerException in the
ownersMap.put("owners", userManager.getUsers(); line of the referenceData method.
Edit:
UserManagerImpl
#Service(value = "userManager")
public class UserManagerImpl implements UserManager {
#Autowired
UserDao dao;
public void setUserDao(UserDao dao) {
this.dao = dao;
}
public List getUsers() {
return dao.getUsers();
}
public User getUser(String userId) {
return dao.getUser(Long.valueOf(userId));
}
public void saveUser(User user) {
dao.saveUser(user);
}
public void removeUser(String userId) {
dao.removeUser(Long.valueOf(userId));
}
}
Robot.java
public class Robot extends BaseObject {
private static final long serialVersionUID = -1932852212232780150L;
private Long id;
private String name;
private Date birthday;
private User owner;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public User getOwner() {
return owner;
}
public void setOwner(User owner) {
this.owner = owner;
}
}
The only thing I am seeing why you are having a null in there is that 'userManager' has not been instantiated.
You can check if it has been through (userManager == null).
Your code looks good to me, not looking into the logic. I think your configuration of Spring's IoC is the problem here.
Can you post your *.xml files.
This would help out solve your problems.
Cheers!
Related
I want to test this method:
if (user != null) {
userDAO.delete(user);
sessionService.logout(user.getEmail());
}
}
this is my Test:
public void deleteTest(){
doNothing().when(userDAO).delete(user);
doNothing().when(sessionService).logout("");
sessionInvalidationDecorator.delete(user);
verify(userDAO, times(1)).delete(user);
verify(sessionService, times(1)).logout("");
}
but I get:
Wanted but not invoked:
sessionService.logout("");
-> at de.unibremen.swp.controller.SessionInvalidationDecoratorTest.deleteTest(SessionInvalidationDecoratorTest.java:69)
Actually, there were zero interactions with this mock.
I don't know what I do wrong
Thanks in advance.
The problem was that you are indicating the value that logout must have, in this case when it is mocked, and referring to user.getEmail() is waiting for you to return a " " and instead you are returning a string from getEmail
I guess that your user.getMail does not return the empty string that you verify.
You have to make sure that user.getMail returns a string that is equal to the one you verify.
Here is a complete example that works.
public class SessionInvalidationDecoratorTest {
private UserDAO userDAOMock;
private SessionService sessionServiceMock;
private SessionInvalidationDecorator sessionInvalidationDecorator;
#Before
public void setup() {
userDAOMock = Mockito.mock(UserDAO.class);
sessionServiceMock = Mockito.mock(SessionService.class);
sessionInvalidationDecorator = new SessionInvalidationDecorator(userDAOMock, sessionServiceMock);
}
#Test
public void deleteTest() {
User user = new User();
user.setEmail("mail#some-domain.com");
sessionInvalidationDecorator.delete(user);
Mockito.verify(userDAOMock, Mockito.times(1)).delete(user);
Mockito.verify(sessionServiceMock, Mockito.times(1)).logout("mail#some-domain.com");
}
}
public class SessionInvalidationDecorator {
private UserDAO userDAO;
private SessionService sessionService;
public SessionInvalidationDecorator(UserDAO userDAO, SessionService sessionService) {
this.userDAO = userDAO;
this.sessionService = sessionService;
}
public void delete(User user) {
if (user != null) {
userDAO.delete(user);
sessionService.logout(user.getEmail());
}
}
}
public interface SessionService {
void logout(String email);
}
public interface UserDAO {
void delete(User user);
}
public class User {
private String email;
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
}
How to finish validation with sending all form data in Vaadin 8? Unfortunetly I dont understand binder concept :( I wrote a field validation but what now? It works. My user see when I demand that he fill out a field but is there any easy way to validate my all form? How can I "tell" to my save button that my form is valid?
In my editor Im defining a validator
#SpringComponent
#PrototypeScope
public class VaadinStringEditor extends TextField implements HasValueComponent<String> {
private Binder<String> binder;
BinderValidationStatus<String> status;
#PostConstruct
public void init() {
setWidth("100%");
binder = new Binder<>();
}
#Override
public void initDefaults() {
setValue("");
status = binder.validate();
}
#Override
public void setConfiguration(EditorConfiguration editorConfiguration) {
Validator<String> validator = ((TextFieldConfiguration) editorConfiguration).getValidator();
if (validator != null) {
binder.forField(this).withValidator(validator).asRequired("Mandatory").bind(s -> getValue(),
(b, v) -> setValue(v));
}
public BinderValidationStatus<String> getStatus() {
return status;
}
public void setStatus(BinderValidationStatus<String> status) {
this.status = status;
}
public boolean validate() {
BinderValidationStatus<String> status = binder.validate();
return status.isOk();
}
}
}
I have also an TextEditorConfiguration added:
public class TextFieldConfiguration implements EditorConfiguration {
private Validator<String> validator;
private int validated;
public TextFieldConfiguration(Validator<String> validator) {
this.validator = validator;
}
public TextFieldConfiguration() {
this.validator = null;
}
public Validator<String> getValidator() {
return validator;
}
public int getValidated() {
return validated;
}
public void setValidated(int validated) {
this.validated = validated;
}
}
In my case there are plenty of editors like DateEditor and so on. UI Valtidation works well. Since one month I can not find a way how to connect it to submit button to prevent send a form.
In the form class I have defined all questions
for example:
question = new AseQuestion(AseQuestionId.DATE_OF_NOTIFICATION, EditorType.DATE_EDITOR);
question.setDescription(
"When it happend?");
question.setEditorConfiguration(new DateFieldConfiguration(dateRequiredValidator(), dateNotAllowedValidator()));
return question;
question = new AseQuestion(AseQuestionId.QUESTION2, EditorType.STRING_EDITOR);
question.setDescription("
"Write something");
private Validator<String> textRequiredValidator() {
return Validator.from(v -> v != null && StringUtils.trimAllWhitespace((String) v).length() != 0,
"It cannot be empty!!!");
And the class where I have a submit button
public class QuestionWindow extends Window {
#Autowired
private transient VaadinStringEditor editor;
private Button createSaveButton() {
Button saveButton = new Button(i18n.getWithDefault("newAseQuestions.save", "Übernehmen"));
saveButton.addClickListener(e -> {
if (editor.getBinder.validate()) {
Notification.show("This is the caption OK", "This is the description",
Notification.Type.HUMANIZED_MESSAGE);
} else {
Notification.show("This is the caption", "This is the description",
Notification.Type.HUMANIZED_MESSAGE);
System.out.println("kurwa");
}
saveAse();
});
return saveButton;
}
OK lets assume we haven this POJO:
public class Person {
private String firstname;
private String lastname;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
And we want to edit it.
So we build the following form:
public class Form {
private TextField firstname;
private TextField lastname;
private Binder<Person> binder = new Binder<>();
void bindFields() {
binder.forField(firstname).withValidator(textRequiredValidator())
.asRequired("Mandatory").bind(Person::getFirstname, Person::setFirstname);
binder.forField(lastname).withValidator(textRequiredValidator())
.asRequired("Mandatory").bind(Person::getLastname, Person::setLastname);
}
public void setDatasource(Person person) {
binder.setBean(person);
}
private Validator<String> textRequiredValidator() {
return Validator.from(v -> v != null && StringUtils.trimAllWhitespace((String) v).length() != 0,
"It cannot be empty!!!");
}
public boolean validate() {
BinderValidationStatus<Person> status = binder.validate();
return status.isOk();
}
}
In order to use this form we need to call bindFields first (e.g. constructor, init).
Than a controller or so calls setDatasource with the person we want to edit.
After this the user can fill or edit the form and when the user finishes the status of the form can be retrieved via validate.
If you need the errors from the fields you get them from the BinderValidationStatus.
For more information look at https://vaadin.com/docs/v8/framework/datamodel/datamodel-forms.html
Here is the problem, when I send my object to server using retrofit I got it null. I'm doing this to create the json object:
HashMap<String, UserModel> map = new HashMap<>();
map.put("user", user);
But, when the json arrives in the server I got something like this:
{"user":null}
Then I printed ny json file with this line:
Log.d("TAG", new JSONObject(map).toString());
And I saw the same null object.
So, here is my question, Why is this happening? And how can I fix that?
Here goes some information about my project:
Retrofit version: 2.0.0
Retrofit serializer: jackson version 2.0.0
using also jackson to convert JodaTime version 2.4.0
here goes how I get retrofit instance:
public T buildServiceInstance(Class<T> clazz){
return new Retrofit.Builder().baseUrl(BuildConfig.API_HOST)
.addConverterFactory(JacksonConverterFactory.create())
.build().create(clazz);
}
I call that method here:
public static final IUserApi serviceInstance = new ApiBuildRequester<IUserApi>()
.buildServiceInstance(IUserApi.class);
Method declaration on interface IUserApi:
#POST("User.svc/Save")
Call<ResponseSaveUserApiModel> save(#Body HashMap<String, UserModel> map);
And at last, but I guess, not less important:
public class UserModel implements Parcelable {
private String idUser;
private String name;
private String email;
#JsonProperty("password")
private String safePassword;
private String salt;
private String phoneNumber;
private String facebookProfilePictureUrl;
private String facebookUserId;
public UserModel() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIdUser() {
return idUser;
}
public void setIdUser(String idUser) {
this.idUser = idUser;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getSafePassword() {
return safePassword;
}
public void setSafePassword(String safePassword) {
this.safePassword = safePassword;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getFacebookProfilePictureUrl() {
return facebookProfilePictureUrl;
}
public void setFacebookProfilePictureUrl(String facebookProfilePictureUrl) {
this.facebookProfilePictureUrl = facebookProfilePictureUrl;
}
public String getFacebookUserId() {
return facebookUserId;
}
public void setFacebookUserId(String facebookUserId) {
this.facebookUserId = facebookUserId;
}
#Override
public int describeContents() {
return 0;
}
public UserModel(Parcel in) { // Deve estar na mesma ordem do "writeToParcel"
setIdUser(in.readString());
setName(in.readString());
setEmail(in.readString());
setSafePassword(in.readString());
setPhoneNumber(in.readString());
setFacebookProfilePictureUrl(in.readString());
setFacebookUserId(in.readString());
}
#Override
public void writeToParcel(Parcel dest, int flags) { //Deve estar na mesma ordem do construtor que recebe parcel
dest.writeString(idUser);
dest.writeString(name);
dest.writeString(email);
dest.writeString(safePassword);
dest.writeString(phoneNumber);
dest.writeString(facebookProfilePictureUrl);
dest.writeString(facebookUserId);
}
public static final Parcelable.Creator<UserModel> CREATOR = new Parcelable.Creator<UserModel>(){
#Override
public UserModel createFromParcel(Parcel source) {
return new UserModel(source);
}
#Override
public UserModel[] newArray(int size) {
return new UserModel[size];
}
};
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
}
Debug screen:
#Selvin and #cricket_007 You are the best!
I got this using your hint that my printing was wrong, and I found the solution.
I have two types of users in my app, facebook users or native users, two forms, but just one object, and here was the problem, when I sent facebook objects (complete) it worked fine, but when I tried to send native users, with some null properties, it crashed my serialization.
So I had to check every property before send it, it's just a workaround, but for now it's enough, thank you a lot folks!
When i want to add item to favorite .. i write this code my program and access everywhere: Favorite.add(itemid);
When i want to add item to message i write this code my program and access everywhere: Message.add(itemid);
Two class have some methods. So how i can design this useful?
For example;
AbstractData.addFavorite(itemid);
AbstractData.addMessage(itemid);
or
AbstractData<Fav>.add(itemid);
AbstractData<SMS>.add(itemid);
or
Your opinion?
Thank for help and sory for my little english...
Favorite.class
public class Favorite {
static SparseArray<Fav> LIST = new SparseArray<>();
public static boolean add(int ID){
if(!check(ID)){
LIST.put(ID, new Fav(ID, DateFormat.getDateTimeInstance().format(new Date())));
return true;
}
return false;
}
public static void remove(int ID){
if(LIST.indexOfKey(ID) >= 0 )
LIST.remove(ID);
}
public static boolean check(int ID){return LIST.get(ID) != null;}
public static Fav get(int ID){return LIST.get(ID);}
public static void saveALL(){
AsyncTask.execute(new Runnable() {
#Override
public void run() {
Fav favorite;
for (int i = 0; i < LISTE.size(); i++) {
favorite = get(LISTE.keyAt(i));
if (favorite != null)
//Saving data to xml
}
}
});
Log.d("DONE", "Favorite LIST Saving");
}
}
Fav.class
public class Fav implements IModel{
private int ID;
private String DATE;
public Fav(int ID, String DATE) {
this.ID = ID;
this.DATE = DATE;
}
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public String getDate() {
return DATE;
}
public void setDate(String DATE) {
this.DATE = DATE;
}
}
Message.class
public class Message{
static SparseArray<SMS> LIST = new SparseArray<>();
public static boolean add(int ID){
if(!check(ID)){
LIST.put(ID, new SMS(ID, DateFormat.getDateTimeInstance().format(new Date())));
return true;
}
return false;
}
public static void remove(int ID){
if(LIST.indexOfKey(ID) >= 0 )
LIST.remove(ID);
}
public static boolean check(int ID){return LIST.get(ID) != null;}
public static SMS get(int ID){return LIST.get(ID);}
public static void saveALL(){
AsyncTask.execute(new Runnable() {
#Override
public void run() {
SMS message;
for (int i = 0; i < LISTE.size(); i++) {
message = get(LISTE.keyAt(i));
if (message != null)
//Saving data to xml
}
}
});
Log.d("DONE", "Message LIST Saving");
}
}
SMS.class
public class SMS implements IModel{
private int ID;
private String DATE;
public SMS(int ID, String DATE) {
this.ID = ID;
this.DATE = DATE;
}
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public String getDate() {
return DATE;
}
public void setDate(String DATE) {
this.DATE = DATE;
}
}
IModel.class
public interface IModel {
int getID();
void setID(int ID);
String getDate();
void setDate(String DATE);
}
In my opinion...
Don't over-design your models.
Don't make your add and remove methods static, it will eventually leave you with headaches. You want your constructor to initialize your object.
Either use a Singleton Pattern to get a single instance of your manager object, or
Keep your manager class as a local variable in your Application class, make an access method for it, initialize it in onCreate().
Personally I've started to ditch the getter/setter pattern in favour of public fields, particularly if they're final like in enums. I know this is supposed to be ugly but... I don't care as long as it's convenient =)
So...
public class MyApplication extends Application
{
private static MyApplication instance;
private FavouritesManager favouritesManager;
public static getMyApplicationInstance ()
{
return instance;
}
public void onCreate ()
{
instance = this;
favouritesManager = new FavouritesManager(this); // You may want it to have a Context...
}
}
public class FavouritesManager
{
private Map<Integer,Favourites> favorites;
public FavouritesManager ()
{
load();
}
public void add ( Favourite favourite )
{
favourites.put(favourite.id, favourite);
}
public boolean contains ( int favouriteId )
{
favourites.contaisKey(favouriteId);
}
private void load ()
{
favourites = new HashMap<>();
// Maybe deserialize json from SharedPreferenecs?
}
public List<Favorite> getAll ()
{
// Return all Favourites, sorted by their SortOrder.
}
public Favorite create ( String name )
{
// Maybe a factory method that generates an unused id and returns a new Favourite instance?
}
}
public Favourite
{
public final int id;
public final Date createDate;
public String name;
public int sortOrder;
public Favorite ( int id, String name, int sortOrder )
{
this.id = id;
this.createDate = Date();
this.name = name;
this.sortOrder = sortOrder;
}
}
public class MyActivity extend Activity
{
protected void onCreate ( Bundle savedInstanceState )
{
FavouritesManager favmanager = MyApplication.getMyApplicationInstance().getFavoritesManager();
}
{
}
Make your classes Message and SMS implement the same interface IModel. Then, when you implement your methods (e.g. add()) and want them to accept both Message and SMS objects, use the base interface in your method signature:
public class AbstractData {
public static void add(final IModel data) { // <- Use interface here!
// ...
}
}
Now you can add objects this way:
Message msg = new Message();
AbstractData.add(msg);
SMS sms = new SMS();
AbstractData.add(sms);
I'm currently writing a REST api using Jackson (2.4.0-rc3) and spring mvc (4.0.3), and I'm trying to make it secure.
In this way, I try to use JsonView to select the parts of the objects that can be serialized.
I've found the solution (which is not for me) to annotate my Controller method with the view I want. But I'd like to select on the fly the view inside the controller.
Is it possible to extend the ResponseEntity class in order to specify which JsonView I want ?
A little piece of code :
Here is the account class
public class Account {
#JsonProperty(value = "account_id")
private Long accountId;
#JsonProperty(value = "mail_address")
private String mailAddress;
#JsonProperty(value = "password")
private String password;
#JsonProperty(value = "insert_event")
private Date insertEvent;
#JsonProperty(value = "update_event")
private Date updateEvent;
#JsonProperty(value = "delete_event")
private Date deleteEvent;
#JsonView(value = PublicView.class)
public Long getAccountId() {
return accountId;
}
#JsonView(value = PublicView.class)
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
#JsonView(value = OwnerView.class)
public String getMailAddress() {
return mailAddress;
}
#JsonView(value = OwnerView.class)
public void setMailAddress(String mailAddress) {
this.mailAddress = mailAddress;
}
#JsonIgnore
public String getPassword() {
return password;
}
#JsonView(value = OwnerView.class)
public void setPassword(String password) {
this.password = password;
}
#JsonView(value = AdminView.class)
public Date getInsertEvent() {
return insertEvent;
}
#JsonView(value = AdminView.class)
public void setInsertEvent(Date insertEvent) {
this.insertEvent = insertEvent;
}
#JsonView(value = AdminView.class)
public Date getUpdateEvent() {
return updateEvent;
}
#JsonView(value = AdminView.class)
public void setUpdateEvent(Date updateEvent) {
this.updateEvent = updateEvent;
}
#JsonView(value = AdminView.class)
public Date getDeleteEvent() {
return deleteEvent;
}
#JsonView(value = OwnerView.class)
public void setDeleteEvent(Date deleteEvent) {
this.deleteEvent = deleteEvent;
}
#JsonProperty(value = "name")
public abstract String getName();
}
Here is the account controller
#RestController
#RequestMapping("/account")
public class AccountCtrlImpl implements AccountCtrl {
#Autowired
private AccountSrv accountSrv;
public AccountSrv getAccountSrv() {
return accountSrv;
}
public void setAccountSrv(AccountSrv accountSrv) {
this.accountSrv = accountSrv;
}
#Override
#RequestMapping(value = "/get_by_id/{accountId}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseEntity<Account> getById(#PathVariable(value = "accountId") Long accountId) {
try {
return new ResponseEntity<Account>(this.getAccountSrv().getById(accountId), HttpStatus.OK);
} catch (ServiceException e) {
return new ResponseEntity<Account>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#Override
#RequestMapping(value = "/get_by_mail_address/{mail_address}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseEntity<Account> getByMailAddress(#PathVariable(value = "mail_address") String mailAddress) {
try {
return new ResponseEntity<Account>(this.getAccountSrv().getByMailAddress(mailAddress), HttpStatus.OK);
} catch (ServiceException e) {
return new ResponseEntity<Account>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#Override
#RequestMapping(value = "/authenticate/{mail_address}/{password}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseEntity<Account> authenticate(#PathVariable(value = "mail_address") String mailAddress, #PathVariable(value = "password") String password) {
return new ResponseEntity<Account>(HttpStatus.NOT_IMPLEMENTED);
}
}
I really like the solution presented here to dynamically select a json view inside your controller method.
Basically, you return a MappingJacksonValue which you construct with the value you want to return. After that you call setSerializationView(viewClass) with the proper view class. In my use case, I returned a different view depending on the current user, something like this:
#RequestMapping("/foos")
public MappingJacksonValue getFoo(#AuthenticationPrincipal UserDetails userDetails ) {
MappingJacksonValue value = new MappingJacksonValue( fooService.getAll() );
if( userDetails.isAdminUser() ) {
value.setSerializationView( Views.AdminView.class );
} else {
value.setSerializationView( Views.UserView.class );
}
return value;
}
BTW: If you are using Spring Boot, you can control if properties that have no view associated are serialized or not by setting this in your application.properties:
spring.jackson.mapper.default_view_inclusion=true
I've solved my problem extending ResponseEntity like this :
public class ResponseViewEntity<T> extends ResponseEntity<ContainerViewEntity<T>> {
private Class<? extends BaseView> view;
public ResponseViewEntity(HttpStatus statusCode) {
super(statusCode);
}
public ResponseViewEntity(T body, HttpStatus statusCode) {
super(new ContainerViewEntity<T>(body, BaseView.class), statusCode);
}
public ResponseViewEntity(T body, Class<? extends BaseView> view, HttpStatus statusCode) {
super(new ContainerViewEntity<T>(body, view), statusCode);
}
}
and ContainerViewEntity encapsulate the object and the selected view
public class ContainerViewEntity<T> {
private final T object;
private final Class<? extends BaseView> view;
public ContainerViewEntity(T object, Class<? extends BaseView> view) {
this.object = object;
this.view = view;
}
public T getObject() {
return object;
}
public Class<? extends BaseView> getView() {
return view;
}
public boolean hasView() {
return this.getView() != null;
}
}
After that, we have convert only the object with the good view.
public class JsonViewMessageConverter extends MappingJackson2HttpMessageConverter {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (object instanceof ContainerViewEntity && ((ContainerViewEntity) object).hasView()) {
writeView((ContainerViewEntity) object, outputMessage);
} else {
super.writeInternal(object, outputMessage);
}
}
protected void writeView(ContainerViewEntity view, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = this.getJsonEncoding(outputMessage.getHeaders().getContentType());
ObjectWriter writer = this.getWriterForView(view.getView());
JsonGenerator jsonGenerator = writer.getFactory().createGenerator(outputMessage.getBody(), encoding);
try {
writer.writeValue(jsonGenerator, view.getObject());
} catch (IOException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
private ObjectWriter getWriterForView(Class<?> view) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
return mapper.writer().withView(view);
}
}
And to finish, I enable the converter
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="wc.handler.view.JsonViewMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
And that's it, I can select the View in the controller
#Override
#RequestMapping(value = "/get_by_id/{accountId}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseViewEntity<Account> getById(#PathVariable(value = "accountId") Long accountId) throws ServiceException {
return new ResponseViewEntity<Account>(this.getAccountSrv().getById(accountId), PublicView.class, HttpStatus.OK);
}
FYI, Spring 4.1 already supported using #JsonView directly on #ResponseBody and ResponseEntity:
Jackson’s #JsonView is supported directly on #ResponseBody and ResponseEntity controller methods for serializing different amounts of detail for the same POJO (e.g. summary vs. detail page). This is also supported with View-based rendering by adding the serialization view type as a model attribute under a special key.
And in http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-jsonview you can find the much simpler solution:
#RestController
public class UserController {
#RequestMapping(value = "/user", method = RequestMethod.GET)
#JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
}
}
public class User {
public interface WithoutPasswordView {};
public interface WithPasswordView extends WithoutPasswordView {};
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
#JsonView(WithoutPasswordView.class)
public String getUsername() {
return this.username;
}
#JsonView(WithPasswordView.class)
public String getPassword() {
return this.password;
}
}
This works great :
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public void getZone(#PathVariable long id, #RequestParam(name = "tree", required = false) boolean withChildren, HttpServletResponse response) throws IOException {
LOGGER.debug("Get a specific zone with id {}", id);
Zone zone = zoneService.findById(id);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
if (withChildren) {
response.getWriter().append(mapper.writeValueAsString(zone));
} else {
response.getWriter().append(mapper.writerWithView(View.ZoneWithoutChildren.class).writeValueAsString(zone));
}
}