Flexible marshalling with JAXB - jpa-2.0

I'm hoping to have a flexible way of marshalling objects. A verbose version for single objects and a less-verbose version for multiple object versions.
For example, consider my department model:
GET /locations/1:
<location id='1'>
<link rel="self" href="http://example.com/locations/1"/>
<link rel="parent" href="http://example.com/serviceareas/1"/>
<name>location 01</name>
<departments>
<department id='1'>
<link rel="self" href="http://example.com/departments/1"/>
<name>department 01</name>
</department>
<department id='2'>
<link rel="self" href="http://example.com/departments/2"/>
<name>department 02</name>
</department>
<department id='3'>
<link rel="self" href="http://example.com/departments/3"/>
<name>department 03</name>
</department>
</departments>
</location>
GET /department/1:
<department id='1'>
<link rel="self" href="http://example.com/departments/1"/>
<link rel="parent" href="http://example.com/locations/1"/>
<name>department 01</name>
<abbr>dept 01</abbr>
....
<specialty>critical care</specialty>
</department>
Is there a way to do this? Would I need to have separate entity objects? One that references the table for CRUD operations and another for lists?

Note: I'm the EclipseLink JAXB (MOXy) lead, and a member of the JAXB 2 (JSR-222) expert group.
Your question is tagged EclipseLink, if you are using EclipseLink JAXB (MOXy) you can take advantage of the external binding document to apply a second mapping to the Department class.
ContextResolver
In a JAX-RS environment you can leverage MOXy's external binding document through a ContextResolver:
import java.io.*;
import java.util.*;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
#Provider
#Produces({"application/xml", "application/json"})
public class DepartmentContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext jc;
public DepartmentContextResolver() {
try {
Map<String, Object> props = new HashMap<String, Object>(1);
props.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "example/bindings.xml");
jc = JAXBContext.newInstance(new Class[] {Department.class} , props);
} catch(JAXBException e) {
throw new RuntimeException(e);
}
}
public JAXBContext getContext(Class<?> clazz) {
if(Department.class == clazz) {
return jc;
}
return null;
}
}
For More Information
http://blog.bdoughan.com/2011/04/moxys-xml-metadata-in-jax-rs-service.html
http://blog.bdoughan.com/2010/08/creating-restful-web-service-part-35.html
External Binding Document
By default MOXy's external binding document is used to augment the annotated model, but if you set the xml-mapping-metadata-complete flag it will completely override the annotations allowing you to apply a completely different mapping:
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="example"
xml-mapping-metadata-complete="true">
...
</xml-bindings>
For More Information
http://blog.bdoughan.com/2011/09/mapping-objects-to-multiple-xml-schemas.html
http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html
UPDATE
This update is to address a number of questions you asked in one of your comments:
1 . Should/can each ContentResolver have its own binding file?
Yes each ContextResolver should have its own binding file. The main reason for introducing a new ContextResolver is to represent a secondary mapping.
2 . Can I have more than one for each ContentResolver (this would give me a number of renderings of the same class, creating a 'view' of sorts), perhaps specifying its location in the constructor?
For a single ContextResolver you can express the metadata across multiple binding files, but they will be combined into a single set of mappings. This means that a single ContentResolver cannot have multiple views of a single class. A separate ContextResolver is used to represent a secondary mapping.
3 . Where should the binding files reside?
I recommend loading the metadata file from the class path.
http://blog.bdoughan.com/2011/04/moxys-xml-metadata-in-jax-rs-service.html
4 . I can see how the ContentResolver could be easily specified in a Resource's GET method, but how would this be done if the object is embedded in another (JPA) object? In the embedded object's getter/setter?
Your JAX-RS implementation should pick up your ContextResolver because it is annotated with #Provider. The ContextResolver used for a class will depend on how you implement the getContext method:
public JAXBContext getContext(Class<?> clazz) {
if(Customer.class == clazz) {
return jc;
}
return null;
}

Here comes another idea. Could be a bad idea but somewhat easy.
class Department {
#XmlElement(required = true)
public Link getSelf() {
return self;
}
#XmlElement(required = false) // default
public Link getParent() {
if (verbose) {
return parent;
}
return null;
}
#XmlElement(required = false) // default
public String getSpecialty() {
if (verbose) {
return specialty;
}
return null;
}
#XmlTransient
private boolean verbose;
}

Related

Camel XSLT transformation

I am trying to use the XSLT component to do dynamic transformation from XML. Is it possible to pass in a java variable in the URI as XSLT template?
For example:
from("direct:foo").
to("xslt:${fee}").
to("direct:output");
foo - is a XML payload,
fee - XSLT template stored as java.lang.String,
output - xml payload
You can assign your variable into message header by some conditions:
.setHeader("TemplateLocation").constant("OSGI-INF/xsl/pretty.xsl")
After, you can use Recipient List EIP:
.recipientList().simple("xslt:${header.TemplateLocation}")
or you can use toD:
.toD("xslt:${header.TemplateLocation}")
Working example:
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.routeId("xsltTest")
.log(LoggingLevel.INFO, "XML input : \n${body}")
.setHeader("TemplateLocation").constant("OSGI-INF/xsl/pretty.xsl")
//.recipientList().simple("xslt:${header.TemplateLocation}")
.toD("xslt:${header.TemplateLocation}")
.to("log:end?level=INFO&showAll=true&multiline=true");
}
};
}
And there is no way to use a string variable as an xslt template, as far as I know.
Based on my knowledge
Your XSLT poller has a dynamic expression as subdirectory (${fee}).
As far as I know you cannot have a dynamic from address in a Camel
route. Because this would mean that you could consume from multiple
endpoints.
You can have it as separate file and call it like this
to("xslt:file:///foo/bar.xsl").
For more details XSLT
You cannot use a dynamic stylesheet (dynamic content) with the XSL component of Camel.
The most dynamic thing you can do is a dynamic reference to a static file like this:
.toD("xslt:${expressionWithFileReference}")
However, you can simply call a Java bean to do what you want and call it from the route:
.bean(javaBeanReference or new YourJavaBean())
In the Bean you can use Camel annotations to inject header(s), properties and the body into a method. Whatever you need from the current Camel Exchange.
public void yourMethod(
#Header(headername) String parameterName,
#Body Type parameterName) {
...
}
As Camel does not have support for dynamic XSLT input stream, I had to create my own Transformer. This might help someone
Here is my code snippet. I used camel processor as below
#Override
public void process(Exchange exchange) throws Exception {
XmlMapper xmlMapper = new XmlMapper();
Target target = xmlMapper.readValue(transform(getInputStreamFromDocument(xmlPayload), new ByteArrayInputStream(xsltTemplate.getBytes())), target.class);
}
public byte[] transform(InputStream dataXML, InputStream inputXSL)
throws TransformerException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource(inputXSL));
StreamSource in = new StreamSource(dataXML);
StreamResult out = new StreamResult(bos);
transformer.transform(in, out);
return bos.toByteArray();
}

Is there a way to get the Jersey automatically generated plural element name around a collection of JAXB elements with EclipseLink Moxy?

I am using the Jersey 2.1 API to return lists of JAXB annotationed objects.
I have a class Person
#XmlRootElement(name = "person")
public class Person { ...
In the Jersey API, when I return a List of Person and have the output set to xml, it creates a wrapper called <People> around my list:
<People>
<Person>
.. fields
</Person>
</People>
when I set the output to JSON format it does not add this extra People wrapper and I would like it to. I am using EclipseLink Moxy as the JSON provider. Is there a way to get the JSON output to look the same as the XML?
I came across a field for the Jersey 1.X API called FEATURE_XMLROOTELEMENT_PROCESSING that is supposed to enable this, but I don't know how to set this in 2.x. And the fact that it is doing it for XML output seems to indicate that it is already set. I just need to get the JSON to be the same!
Any help would be appreciated, thanks!
You could do the following:
Java Model
You could introduce a new class called People into your object model.
People
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="People")
#XmlAccessorType(XmlAccessType.FIELD)
public class People {
#XmlElementRef
private List<Person> person;
}
Person
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="Person")
public class Person {
}
RESTful Service
Instead of:
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Person> read() {
You would do:
#GET
#Produces(MediaType.APPLICATION_JSON)
public People read() {
By default MOXy won't included the root element. When working with Jersey you can leverage the MoxyJsonConfig object (see: http://blog.bdoughan.com/2013/06/moxy-is-new-default-json-binding.html).
import javax.ws.rs.ext.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.glassfish.jersey.moxy.json.MoxyJsonConfig;
#Provider
public class MOXyJsonContextResolver implements ContextResolver<MoxyJsonConfig> {
private final MoxyJsonConfig config;
public MOXyJsonContextResolver() {
config = new MoxyJsonConfig()
.setIncludeRoot(true);
}
#Override
public MoxyJsonConfig getContext(Class<?> objectType) {
return config;
}
}
You can also leverage MOXy's MOXyJsonProvider class to do the same configuration:
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html

Change Template of Uploaded File in Media Library in Sitecore

I've done some research into this but not sure I understand all the pieces that need to go into the following problem.
My client needs a special template to be used instead of the auto detected media templates in the Media Library if they upload to a certain Folder. The template has special fields. The template can also house different types of files (PDFs, vendor specific formats, executables).
For development purposes we are currently uploading the file and then doing a template switch afterwards but what really needs to happen is that file be uploaded to that template type in the first place. I was wondering if there was a way to hook into the upload process to make sure the special template is used when underneath a certain path in the Media Library? If so, where should I start?
We recently had to do something similar. Along the same line as techphoria414, I'd tap into the upload save pipeline. Then, to make it a bit more generic and reusable, use the power of Sitecore's configuration parsing to hook everything up to your handler. Here's what we ended up with.
Main class with required "Process" method:
public class ChangeTemplate
{
public string Name { get; set; }
public string Path { get; set; }
public List<ChangedMediaTemplate> Templates { get; set; }
public ChangeTemplate()
{
Templates = new List<ChangedMediaTemplate>();
}
public void Process(UploadArgs args)
{
var db = Sitecore.Context.ContentDatabase;
var uploadPath = db.GetItem(args.Folder).Paths.ContentPath;
if (!uploadPath.StartsWith(Path))
{
// Not uploading to designated folder
return;
}
foreach (var item in args.UploadedItems)
{
// Need to change template for this item?
var changedTemplate = Templates.Where(t => t.Old.Equals(item.Template.FullName)).FirstOrDefault();
if (changedTemplate != null)
{
var newTemplate = db.Templates[changedTemplate.New];
try
{
item.ChangeTemplate(newTemplate);
}
catch (Exception e)
{
Log.Error("Unable to change {0} template on upload of {1} to {2}.".FormatWith(Name, item.Name, uploadPath), e, this);
}
}
}
}
}
Minor supporting class:
public class ChangedMediaTemplate
{
public string Old { get; set; }
public string New { get; set; }
}
And then the config:
<processors>
<uiUpload>
<processor patch:after="*[#type='Sitecore.Pipelines.Upload.Save, Sitecore.Kernel']" mode="on" type="Foo.Project.SitecoreX.Pipelines.Upload.ChangeTemplate, Foo.Project.Classes">
<Name>Product Images</Name>
<Path>/sitecore/media library/Images/Foo/products</Path>
<Templates hint="list">
<Template type="Foo.Project.SitecoreX.Pipelines.Upload.ChangedMediaTemplate, Foo.Project.Classes">
<Old>System/Media/Unversioned/Image</Old>
<New>User Defined/Foo/Product/Image/Unversioned/Product Image</New>
</Template>
<Template type="Foo.Project.SitecoreX.Pipelines.Upload.ChangedMediaTemplate, Foo.Project.Classes">
<Old>System/Media/Unversioned/Jpeg</Old>
<New>User Defined/Foo/Product/Image/Unversioned/Product Jpeg</New>
</Template>
<Template type="Foo.Project.SitecoreX.Pipelines.Upload.ChangedMediaTemplate, Foo.Project.Classes">
<Old>System/Media/Versioned/Image</Old>
<New>User Defined/Foo/Product/Image/Versioned/Product Image</New>
</Template>
<Template type="Foo.Project.SitecoreX.Pipelines.Upload.ChangedMediaTemplate, Foo.Project.Classes">
<Old>System/Media/Versioned/Jpeg</Old>
<New>User Defined/Foo/Product/Image/Versioned/Product Jpeg</New>
</Template>
</Templates>
</processor>
</uiUpload>
</processors>
Modifying or adding new template rules becomes as simple as editing the config as needed.
Hope this helps!
Unfortunately, to my knowledge, the Sitecore.Resources.Media.MediaCreator(handels mediaitem creation) can not be overridden. So the only (easy) way is to change the templates for the entire media library.
Otherwise I think you need to make your own changes to the sheerUI - but i wouldn't recommend it. Anyhow.. the mediaitems Sitecore creates, are defined in web.config under
<mediaLibrary>
<mediaTypes>
<mediaType name="Any" extensions="*">...</mediaType>
<mediaType name="Windows Bitmap image" extensions="bmp">...</mediaType>
....
</mediaTypes>
</mediaLibrary>
There is a version/unversion template for each mediaitem you can change.
If you want to look into SheerUI I recommend you start here:
http://learnsitecore.cmsuniverse.net/en/Developers/Articles/2009/10/My-First-Sitecore-XAML-Application.aspx
I would use an item:saving handler. If the item is a Media Item, and within a configured folder, then you can change its template. As always with item:saving, insert some checks very early in the method and exit quickly if you determine the item is not of concern.
I want to add something to ambrauer's answer above. It is a good solution but the code should be tweaked before use in production.
The following line:
var uploadPath = db.GetItem(args.Folder).Paths.ContentPath;
should be changed to:
if (args.Folder == null) return;
var uploadFolderItem = db.GetItem(args.Folder);
if (uploadFolderItem == null) return;
var uploadPath = uploadFolderItem.Paths.ContentPath;
The reason is that without the null check on args.Folder, the package upload tool in the Sitecore installation wizard breaks.
This is not critical to developers like us but some administrators rely on this tool as part of their workflow if they do not have full access to the site.

How can I instruct a JAXB Marshaller to scrub base64Binary data only for auditing?

I am working in a JAXWS/JAXB web service environment. JAXWS out of the box uses uses the JAXB to marshal/unmarshaler the web service payloads.
I also have a requirement to audit all request and response payloads.
I want a compact and concise marshaled representation of the payload for the audit (as a irrelevant side note - I am auditing using a java.util.concurrent.BlockingQueue and some consumer threads to put batches of audit data in the audit datasource).
I have binary content(mtom) included on some web service response payloads but I DO NOT want to marshal audit these because the serialized base64 would be too large.
So my need is to create a marshaller (exclusively for auditng) that in all cases will scrub binary content but then NOT scrub for the prime purpose of marshalling web service response payloads. I do XSD to Java xjc. I need to use the same XSD/JAXB namespace for both contexts/marshallers.
Java type converter:
<jxb:javaType name=""
parseMethod="com.xxx.xxx.ws.converter.XXXLongConverter.parseXXXLong"
printMethod="com.xxx.xxx.ws.converter.XXXLongConverter.printXXXLong" />
is will not work because 1. I would need to unregister the adapter http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/Marshaller.html#setAdapter%28java.lang.Class,%20A%29
for the marshaller and I don't THINK I have a hook into that for JAXWS. 2. I can't be guaranteed the class name that JAXB will decide to create in order to unregister it.
I created my own XMLAdapter and used the annox jaxb plugin
but that didn't really work for the same reasons the above didn't work.
Update: I now tried manually and reflectively walking through payload(to be audited) prior to marshalling to scrub the binary data but that got to be too much pain for what it was worth.
I should also mention that for brevity of the audit I am using jersey JSON serialization supporting JAXB
but I don't think that takes away or adds to my base problem:
How can I scrub data in one marshaller/unmarshaller but not another but both whose origin is the same JAXB context?
UPDATE: Never figured out an elegate way to do this. Not really possible at this point with the frameworks as they are. UPDATE: Not true. Extending AttachmentMarshaller (I like this a lot and will use it) or creating a "need-aware" XmlAdapter would work for the audit specific marshaller as #Blaise answers below.
UPDATE: If I may take this a step further to round out my use case...I mentioned above that for brevity of the audit I'd like to do Json Serialization of the JSONJAXBContext using jersey apis, specifically using the JSONMarshaller but the interface does not define setAdapter and setAttachmentMarshaller. Coming out of JSONJAXBContext.createJSONMarshaller() is a JSONMarshallerImpl implementation which do define these this methods. I will grudgingly cast to impl so I can set my custom attachment marshaller.
How can I scrub data in one marshaller/unmarshaller but not another
but both whose origin is the same JAXB context?
You could set your own implementation of AttachemntMarshaller and set it on the Marshaller that you are using for auditing.
Root
Below is a sample domain object with a byte[] property that by default will be represented as an element of type base64Binary.
package forum8914008;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
byte[] bytes;
}
Demo
The demo code below first marshals the object to XML, and then marshals it a second time with a custom impplementation of AttachmentMarshaller set.
package forum8914008;
import javax.activation.DataHandler;
import javax.xml.bind.*;
import javax.xml.bind.attachment.AttachmentMarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.bytes = "Hello World".getBytes();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
marshaller.setAttachmentMarshaller(new AttachmentMarshaller() {
#Override
public boolean isXOPPackage() {
return true;
}
#Override
public String addMtomAttachment(DataHandler arg0, String arg1,
String arg2) {
return "fake";
}
#Override
public String addMtomAttachment(byte[] arg0, int arg1, int arg2,
String arg3, String arg4, String arg5) {
return "fake";
}
#Override
public String addSwaRefAttachment(DataHandler arg0) {
return "fake";
}
});
marshaller.marshal(root, System.out);
}
}
Output
Below is the output from running the demo code. The first XML document could grow to be quite large if the byte[] was big. The second XML document would stay the same size.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bytes>SGVsbG8gV29ybGQ=</bytes>
</root>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bytes>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="fake"/>
</bytes>
</root>
How can I scrub data in one marshaller/unmarshaller but not another
but both whose origin is the same JAXB context?
You could support this use case with an XmlAdapter.
XmlAdapter (ByteArrayAdapter)
The following XmlAdapter is used to convert a byte[] to a byte[]. In its default state it will return the original byte[], it also has a audit state where it will return an empty byte[].
package forum8914008;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class ByteArrayAdapter extends XmlAdapter<byte[], byte[]> {
private boolean audit;
public ByteArrayAdapter() {
this(false);
}
public ByteArrayAdapter(boolean audit) {
this.audit = audit;
}
#Override
public byte[] marshal(byte[] bytes) throws Exception {
if(audit) {
return new byte[0];
}
return bytes;
}
#Override
public byte[] unmarshal(byte[] bytes) throws Exception {
return bytes;
}
}
package-info
The #XmlJavaTypeAdapter annotation is used tp register the XmlAdapter. When used at the package level it will apply to all properties of the specified type in that package (see: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html).
#XmlJavaTypeAdapter(value=ByteArrayAdapter.class, type=byte[].class)
package forum8914008;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
Root
Below is a sample domain object with a byte[] property that by default will be represented as an element of type base64Binary.
package forum8914008;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
byte[] bytes;
}
Demo
The demo code below first marshals the object with the default state of the ByteArrayAdapter which will return the real byte[] and the marshals the object a second time with a stateful ByteArrayAdapter set which will convert all byte[] values to an empty byte[].
package forum8914008;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.bytes = "Hello World".getBytes();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
marshaller.setAdapter(new ByteArrayAdapter(true));
marshaller.marshal(root, System.out);
}
}
Output
Below is the output from running the demo code. The XmlAdapter would apply to all mapped fields/properties of type byte[].
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bytes>SGVsbG8gV29ybGQ=</bytes>
</root>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bytes></bytes>
</root>

Spring MVC: SessionAttributes and List

I am facing an issue with spring and it goes as follows:
In SessionAttributes I have an object person with an attribute addresses which is a list. Whenever person is updated via controler, previous entries still remain. So for example if I had in person addresses: old address 1, old address 2, old address 3 and I update person via form to have only one new address, list of addresses becomes: new address 1, old address 2, old address 3 while intended behavior is to have "new address 1" only. I couldn't seem to find a workaround this problem. I am using Spring 3.0.X.
Please find below all related code that shows the issue on hand.
Person.java:
package com.convert.dashboard.web.test;
import java.util.List;
public class Person {
private String name;
private Integer age;
private List<String> addresses;
public Person(List<String> addresses) {
this.addresses = addresses;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getAddresses() {
return addresses;
}
public void setAddresses(List<String> addresses) {
this.addresses = addresses;
}
}
TestController.java
package com.convert.dashboard.web.test;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
#Controller
#RequestMapping("/test")
#SessionAttributes("person")
public class TestController {
#RequestMapping(value = "/")
public ModelAndView xyz() {
ModelAndView mav = new ModelAndView();
List<String> abc = new ArrayList<String>();
abc.add("old address1");
abc.add("old address2");
abc.add("old address3");
Person person = new Person(abc);
mav.addObject("person", person);
mav.setViewName("cForm");
return mav;
}
#RequestMapping("/save")
public #ResponseBody
String process(#ModelAttribute("person") Person person) {
return "<body>" + " Name:" + person.getName() + " Age: " + person.getAge() + " Addresses: " + person.getAddresses();
}
}
cForm.jsp:
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%# taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>populate form</title>
</head>
<body>
<form:form modelAttribute="person" action="/dashboard/test/save">
<form:hidden path="name" value="X" />
<form:hidden path="age" value="20" />
<form:hidden path="addresses[0]" value="New address" />
<input type="Submit" value="Submit" />
</form:form>
</body>
</html>
There are a couple of design issues I would like to address.
You are using a single object for both form binding and domain data. This tends to cause problems exactly like the one you've encountered here. The problem is not that the form fails to "clear" out the addresses of the session object; the problem is that the session object leaks its data structure to the form, which causes binding problems.
The form has knowledge about the contents of the Person object. Specifically, the form expects there to be three addresses in the person.getAddresses() list. Like (1) above, the problem is a leak of the domain structure into the view layer.
I recommend that you create two different "person" classes: one to represent the domain data (the session object), and one to exactly mirror the structure of the form (the form binding object). Your form will contain fields that map directly to properties in a PersonForm class, and in your TestController you can take the data from the PersonForm and update the session Person appropriately. Then the form inputs don't need to be designed to handle different states of the Person.addresses list.
This approach does require a bit more code, but not terribly much, and the savings in form complexity and form/domain decoupling is well worth it.
So the solution goes as follows:
By appending an AJAX call on remove on the client side and adding the code below to the controller.
#RequestMapping("/remeveAddress")
public #ResponseBody String removeElement(#ModelAttribute("person") Person person, #RequestParam Integer addressId, Model model){
person.getAddresses().remove(addressId);
model.addAttribute("person", person);
return "{}";
}
I was facing the same issue and solved it by introducing an init binder to the controller.
The init binder resets the list of addresses for the session attribute.
Depending on requirements it could do some more sophisticated job.
#InitBinder
public void initBinder(HttpSession session) {
Person person = (Person)session.getAttribute("person");
person.getAddresses().clear();
}
Hope that could help someone else :)
Recently I've faced with the same problem, and I've found a very simple solution.
So you need just to add a hidden field for your list.
Spring conversion service will set empty list from the blank string value of this hidden field :)
You can see the solution below:
<input type='hidden' name='addresses' value='' />
And after that put your other code:
<form:input path="addresses[0]" />
<form:input path="addresses[n]" />
Notes:
If you need to convert String to List of YourClass'es, and you haven't own convertors or editors for that.
So make sure that at least ObjectToObjectConverter can do it, YourClass must have constructor with a String arg or the static method valueOf(String).
Yet another possible solution is to use a handler interceptor as follows.
servlet.xml:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/test/**" />
<bean class="com.convert.dashboard.web.test.PersonInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
PersonInterceptor.java:
public class PersonInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
Person person = (Person)request.getSession().getAttribute("person");
if (person != null)
person.getAddresses().clear();
return super.preHandle(request, response, handler);
}
}