SpringBoot java Collection annotation for Swagger - rest

I have a problem with annotations and generating documentation (spring-boot, springfox-swagger2).
What should be a proper annotation for String Collection in my TechnologyDTO?
Does swagger support java.util.Collection?
After generation I have:
Model schema:
{
"technologies": {},
"userGUID": "string"
}
Model:
TechnologyDTO {
technologies (Collection«string»): User main technology names,
userGUID (string): User guid
}
Collection«string» {
}
My TechnologyDTO:
public class TechnologyDTO {
private final String userGUID;
private final Collection<String> technologies;
public TechnologyDTO(String userGUID, Collection<String> technologies) {
this.userGUID = userGUID;
this.technologies = technologies;
}
#ApiModelProperty(notes = "User guid", dataType="string", required = true)
public String getUserGUID() {
return userGUID;
}
#ApiModelProperty(notes = "User main technology names", required = true)
public Collection<String> getTechnologies() {
return technologies;
}
public static TechnologyDTO createEmptyDTO() {
return new TechnologyDTO(null, null);
}
}

Related

How to map enum to String using Mapstruct

I can find answers where we have String to Enum mapping but I can't find how can I map an Enum to a String.
public class Result {
Value enumValue;
}
public enum Value {
TEST,
NO TEST
}
public class Person {
String value;
}
How can I map this ?
I tried :
#Mapping(target = "value", source = "enumValue", qualifiedByName = "mapValue")
#Named("mapValue")
default Person mapValue(final Value en) {
return Person.builder().value(en.name()).build();
}
mapstruct should support this out of the box.
So #Mapping(target = "value", source = "enumValue") should suffice.
Complete example including target/source classes:
#Mapper
public interface EnumMapper {
#Mapping( target = "value", source = "enumValue" )
Person map(Result source);
}
class Result {
private Value enumValue;
public Value getEnumValue() {
return enumValue;
}
public void setEnumValue(Value enumValue) {
this.enumValue = enumValue;
}
}
enum Value {
TEST, NO_TEST
}
class Person {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
This results in the following generated code:
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-02-20T12:33:00+0100",
comments = "version: 1.5.0.Beta2, compiler: Eclipse JDT (IDE) 1.4.50.v20210914-1429, environment: Java 17.0.1 (Azul Systems, Inc.)"
)
public class EnumMapperImpl implements EnumMapper {
#Override
public Person map(Result source) {
if ( source == null ) {
return null;
}
Person person = new Person();
if ( source.getEnumValue() != null ) {
person.setValue( source.getEnumValue().name() );
}
return person;
}
}

MongoRepository Save method does not insert in database

I have created a SpringBoot project with Jhipster. The database I am using is MongoDB.
In the application-dev.yml I have the following configuration:
data:
mongodb:
uri: mongodb://<user>:<pass>#<ip>:<port>
database: gateway
The user, password, ip Address, and port, in my application-dev are real values.
The DatabaseConfiguration.java is:
#Configuration
#EnableMongoRepositories("es.second.cdti.repository")
#Profile("!" + JHipsterConstants.SPRING_PROFILE_CLOUD)
#Import(value = MongoAutoConfiguration.class)
#EnableMongoAuditing(auditorAwareRef = "springSecurityAuditorAware")
public class DatabaseConfiguration {
private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public MongoCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(DateToZonedDateTimeConverter.INSTANCE);
converters.add(ZonedDateTimeToDateConverter.INSTANCE);
return new MongoCustomConversions(converters);
}
#Bean
public Mongobee mongobee(MongoClient mongoClient, MongoTemplate mongoTemplate, MongoProperties mongoProperties) {
log.debug("Configuring Mongobee");
Mongobee mongobee = new Mongobee(mongoClient);
mongobee.setDbName(mongoProperties.getMongoClientDatabase());
mongobee.setMongoTemplate(mongoTemplate);
// package to scan for migrations
mongobee.setChangeLogsScanPackage("es.second.cdti.config.dbmigrations");
mongobee.setEnabled(true);
return mongobee;
}}
The CloudDatabaseConfiguration is:
#Configuration
#EnableMongoRepositories("es.second.cdti.repository")
#Profile(JHipsterConstants.SPRING_PROFILE_CLOUD)
public class CloudDatabaseConfiguration extends AbstractCloudConfig {
private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class);
#Bean
public MongoDbFactory mongoFactory() {
return connectionFactory().mongoDbFactory();
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public MongoCustomConversions customConversions() {
List<Converter<?, ?>> converterList = new ArrayList<>();
converterList.add(DateToZonedDateTimeConverter.INSTANCE);
converterList.add(ZonedDateTimeToDateConverter.INSTANCE);
converterList.add(DurationToLongConverter.INSTANCE);
return new MongoCustomConversions(converterList);
}
#Bean
public Mongobee mongobee(MongoDbFactory mongoDbFactory, MongoTemplate mongoTemplate, Cloud cloud) {
log.debug("Configuring Cloud Mongobee");
List<ServiceInfo> matchingServiceInfos = cloud.getServiceInfos(MongoDbFactory.class);
if (matchingServiceInfos.size() != 1) {
throw new CloudException("No unique service matching MongoDbFactory found. Expected 1, found "
+ matchingServiceInfos.size());
}
MongoServiceInfo info = (MongoServiceInfo) matchingServiceInfos.get(0);
Mongobee mongobee = new Mongobee(info.getUri());
mongobee.setDbName(mongoDbFactory.getDb().getName());
mongobee.setMongoTemplate(mongoTemplate);
// package to scan for migrations
mongobee.setChangeLogsScanPackage("es.second.cdti.config.dbmigrations");
mongobee.setEnabled(true);
return mongobee;
}
}
The cdtiApp.java is:
#SpringBootApplication
#EnableConfigurationProperties({ApplicationProperties.class})
public class CdtiApp implements InitializingBean{
private static final Logger log = LoggerFactory.getLogger(CdtiApp.class);
private final Environment env;
public CdtiApp(Environment env) {
this.env = env;
}
/**
* Initializes cdti.
* <p>
* Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile
* <p>
* You can find more information on how profiles work with JHipster on https://www.jhipster.tech/profiles/.
*/
#PostConstruct
public void initApplication() {
Collection<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
log.error("You have misconfigured your application! It should not run " +
"with both the 'dev' and 'prod' profiles at the same time.");
}
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)) {
log.error("You have misconfigured your application! It should not " +
"run with both the 'dev' and 'cloud' profiles at the same time.");
}
}
/**
* Main method, used to run the application.
*
* #param args the command line arguments.
*/
public static void main(String[] args) {
SpringApplication app = new SpringApplication(CdtiApp.class);
DefaultProfileUtil.addDefaultProfile(app);
Environment env = app.run(args).getEnvironment();
logApplicationStartup(env);
}
private static void logApplicationStartup(Environment env) {
String protocol = "http";
if (env.getProperty("server.ssl.key-store") != null) {
protocol = "https";
}
String serverPort = env.getProperty("server.port");
String contextPath = env.getProperty("server.servlet.context-path");
if (StringUtils.isBlank(contextPath)) {
contextPath = "/";
}
String hostAddress = "localhost";
try {
hostAddress = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.warn("The host name could not be determined, using `localhost` as fallback");
}
log.info("\n----------------------------------------------------------\n\t" +
"Application '{}' is running! Access URLs:\n\t" +
"Local: \t\t{}://localhost:{}{}\n\t" +
"External: \t{}://{}:{}{}\n\t" +
"Profile(s): \t{}\n----------------------------------------------------------",
env.getProperty("spring.application.name"),
protocol,
serverPort,
contextPath,
protocol,
hostAddress,
serverPort,
contextPath,
env.getActiveProfiles());
String configServerStatus = env.getProperty("configserver.status");
if (configServerStatus == null) {
configServerStatus = "Not found or not setup for this application";
}
log.info("\n----------------------------------------------------------\n\t" +
"Config Server: \t{}\n----------------------------------------------------------", configServerStatus);
}
#Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
}
}
The Vehicle entity:
#org.springframework.data.mongodb.core.mapping.Document(collection = "vehicle")
public class Vehicle implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String id;
#NotNull
#Field("plate")
private String plate;
#NotNull
#Field("registrationDate")
private Instant registrationDate;
#NotNull
#Field("brand")
private String brand;
#NotNull
#Field("model")
private String model;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPlate() {
return plate;
}
public void setPlate(String plate) {
this.plate = plate;
}
public Instant getRegistrationDate() {
return registrationDate;
}
public void setRegistrationDate(Instant registrationDate) {
this.registrationDate = registrationDate;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}
The VehicleDTO is:
public class VehicleDTO {
private String id;
private String plate;
private Instant registrationDate;
private String brand;
private String model;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPlate() {
return plate;
}
public void setPlate(String plate) {
this.plate = plate;
}
public Instant getRegistrationDate() {
return registrationDate;
}
public void setRegistrationDate(Instant registrationDate) {
this.registrationDate = registrationDate;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}
The VehicleMapper is:
#Mapper(componentModel = "spring")
public interface VehicleMapper{
Vehicle toEntity(VehicleDTO source);
VehicleDTO toDto(Vehicle target);
}
The VehicleResource is:
#RestController
#RequestMapping("/api")
#CrossOrigin(origins = "*", methods = { RequestMethod.GET, RequestMethod.POST })
public class VehicleResource {
private final Logger log = LoggerFactory.getLogger(VehicleResource.class);
#Value("${jhipster.clientApp.name}")
private String applicationName;
#Autowired
private final VehicleService vehicleService;
public VehicleResource(VehicleService vehicleService) {
this.vehicleService = vehicleService;
}
#PostMapping("/vehicle")
#PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
public ResponseEntity<Vehicle> createVehicle(#Valid #RequestBody VehicleDTO vehicleDTO) throws URISyntaxException {
log.debug("REST request to save Vehicle : {}", vehicleDTO);
Vehicle newVehicle = vehicleService.createVehicle(vehicleDTO);
return ResponseEntity.created(new URI("/api/vehicle/" + newVehicle.getPlate()))
.headers(HeaderUtil.createAlert(applicationName, "vehicleManagement.created", newVehicle.getPlate()))
.body(newVehicle);
}
}
The VehicleService interface is:
public interface VehicleService {
Vehicle createVehicle(VehicleDTO vehicleDTO);
}
The VehicleServiceImpl is:
#Service
public class VehicleServiceImpl implements VehicleService{
#Autowired
private final VehicleRepository vehicleRepository;
#Autowired
private final VehicleMapper mapper;
public VehicleServiceImpl(VehicleRepository vehicleRepository, VehicleMapper mapper) {
this.vehicleRepository = vehicleRepository;
this.mapper = mapper;
}
private final Logger log = LoggerFactory.getLogger(VehicleServiceImpl.class);
#Override
public Vehicle createVehicle(VehicleDTO vehicleDTO) {
Vehicle vehicle = vehicleRepository.save(mapper.toEntity(vehicleDTO));
log.debug("Created Information for vehicle: {}", vehicle);
return vehicle;
}
}
The VehicleRepository interface is:
/**
* Spring Data MongoDB repository for the {#link Vehicle} entity.
*/
#Repository
public interface VehicleRepository extends MongoRepository<Vehicle, String> {
}
From the Swagger console I access the Vehicle-Resource:
Swagger console
Click on the button and write in the text box the json with the vehicle data:
enter JSON data
As we can see in the following image, the answer is 201. Initially the vehicle was saved with the identifier "id": "60e740935ed5a10e2c2ed19e".
Send request
I access the database to check that the vehicle has been correctly stored in the vehicle table. To my surprise ... there is no vehicle in the vehicle table:
show database
I can make sure that the data in the database application-dev is OK. I don't have any other databases.
I suspect that transactions with the database are not actually being made. This data is somehow stored in memory because if I do a findAllVehicles from Swagger it does return the vehicle.
I have a eureka server running (jhipster-registry) and two microservices that synchronize with it. The Gateway, which acts as a reverse proxy and the Vehiculos microservice. The Swagger console is the gateway, from where I make the request to insert vehicles. Everything seems to work, but as I say in bbdd does not save anything.

ASP.NET MVC 2 Authorization with Gateway Page

I've got an MVC 2 application which won't be doing its own authentication, but will retrieve a user ID from the HTTP request header, since users must pass through a gateway before reaching the application.
Once in the app, we need to match up the user ID to information in a "users" table, which contains some security details the application makes use of.
I'm familiar with setting up custom membership and roles providers in ASP.NET, but this feels so different, since the user never should see a login page once past the gateway application.
Questions:
How do I persist the user ID, if at all? It starts out in the request header, but do I have to put it in a cookie? How about SessionState?
Where/when do I get this information? The master page shows the user's name, so it should be available everywhere.
I'd like to still use the [Authorize(Roles="...")] tag in my controller if possible.
We have a very similar setup where I work. As #Mystere Man mentioned, there are risks with this setup, but if the whole infrastructure is setup and running correctly, we have found it to be a secure setup (we do care about security). One thing to ensure, is that the SiteMinder agent is running on the IIS node you're trying to secure, as it will validate an encrypted SMSESSION key also passed in the headers, which will make the requests secure (it would be extremely difficult to spoof the value of the SMSESSION header).
We are using ASP.NET MVC3, which has global action filters, which is what we're using. But with MVC2, you could create a normal, controller level action filter that could be applied to a base controller class so that all of your controllers/actions will be secured.
We have created a custom configuration section that allows us to turn this security filter on and off via web.config. If it's turned off, our configuration section has properties that will allow you to "impersonate" a given user with given roles for testing and debugging purposes. This configuration section also allows us to store the values of the header keys we're looking for in config as well, in case the vendor ever changes the header key names on us.
public class SiteMinderConfiguration : ConfigurationSection
{
[ConfigurationProperty("enabled", IsRequired = true)]
public bool Enabled
{
get { return (bool)this["enabled"]; }
set { this["enabled"] = value; }
}
[ConfigurationProperty("redirectTo", IsRequired = true)]
public RedirectToElement RedirectTo
{
get { return (RedirectToElement)this["redirectTo"]; }
set { this["redirectTo"] = value; }
}
[ConfigurationProperty("sessionCookieName", IsRequired = true)]
public SiteMinderSessionCookieNameElement SessionCookieName
{
get { return (SiteMinderSessionCookieNameElement)this["sessionCookieName"]; }
set { this["sessionCookieName"] = value; }
}
[ConfigurationProperty("userKey", IsRequired = true)]
public UserKeyElement UserKey
{
get { return (UserKeyElement)this["userKey"]; }
set { this["userKey"] = value; }
}
[ConfigurationProperty("rolesKey", IsRequired = true)]
public RolesKeyElement RolesKey
{
get { return (RolesKeyElement)this["rolesKey"]; }
set { this["rolesKey"] = value; }
}
[ConfigurationProperty("firstNameKey", IsRequired = true)]
public FirstNameKeyElement FirstNameKey
{
get { return (FirstNameKeyElement)this["firstNameKey"]; }
set { this["firstNameKey"] = value; }
}
[ConfigurationProperty("lastNameKey", IsRequired = true)]
public LastNameKeyElement LastNameKey
{
get { return (LastNameKeyElement)this["lastNameKey"]; }
set { this["lastNameKey"] = value; }
}
[ConfigurationProperty("impersonate", IsRequired = false)]
public ImpersonateElement Impersonate
{
get { return (ImpersonateElement)this["impersonate"]; }
set { this["impersonate"] = value; }
}
}
public class SiteMinderSessionCookieNameElement : ConfigurationElement
{
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return (string)this["value"]; }
set { this["value"] = value; }
}
}
public class RedirectToElement : ConfigurationElement
{
[ConfigurationProperty("loginUrl", IsRequired = false)]
public string LoginUrl
{
get { return (string)this["loginUrl"]; }
set { this["loginUrl"] = value; }
}
}
public class UserKeyElement : ConfigurationElement
{
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return (string)this["value"]; }
set { this["value"] = value; }
}
}
public class RolesKeyElement : ConfigurationElement
{
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return (string)this["value"]; }
set { this["value"] = value; }
}
}
public class FirstNameKeyElement : ConfigurationElement
{
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return (string)this["value"]; }
set { this["value"] = value; }
}
}
public class LastNameKeyElement : ConfigurationElement
{
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return (string)this["value"]; }
set { this["value"] = value; }
}
}
public class ImpersonateElement : ConfigurationElement
{
[ConfigurationProperty("username", IsRequired = false)]
public UsernameElement Username
{
get { return (UsernameElement)this["username"]; }
set { this["username"] = value; }
}
[ConfigurationProperty("roles", IsRequired = false)]
public RolesElement Roles
{
get { return (RolesElement)this["roles"]; }
set { this["roles"] = value; }
}
}
public class UsernameElement : ConfigurationElement
{
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return (string)this["value"]; }
set { this["value"] = value; }
}
}
public class RolesElement : ConfigurationElement
{
[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get { return (string)this["value"]; }
set { this["value"] = value; }
}
}
So our web.config looks something like this
<configuration>
<configSections>
<section name="siteMinderSecurity" type="MyApp.Web.Security.SiteMinderConfiguration, MyApp.Web" />
...
</configSections>
...
<siteMinderSecurity enabled="false">
<redirectTo loginUrl="https://example.com/login/?ReturnURL={0}"/>
<sessionCookieName value="SMSESSION"/>
<userKey value="SM_USER"/>
<rolesKey value="SN-AD-GROUPS"/>
<firstNameKey value="SN-AD-FIRST-NAME"/>
<lastNameKey value="SN-AD-LAST-NAME"/>
<impersonate>
<username value="ImpersonateMe" />
<roles value="Role1, Role2, Role3" />
</impersonate>
</siteMinderSecurity>
...
</configuration>
We have a custom SiteMinderIdentity...
public class SiteMinderIdentity : GenericIdentity, IIdentity
{
public SiteMinderIdentity(string name, string type) : base(name, type) { }
public IList<string> Roles { get; set; }
}
And a custom SiteMinderPrincipal...
public class SiteMinderPrincipal : GenericPrincipal, IPrincipal
{
public SiteMinderPrincipal(IIdentity identity) : base(identity, null) { }
public SiteMinderPrincipal(IIdentity identity, string[] roles) : base(identity, roles) { }
}
And we populate HttpContext.Current.User and Thread.CurrentPrincipal with an instance of SiteMinderPrincipal that we build up based on information that we pull from the request headers in our action filter...
public class SiteMinderSecurity : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var request = filterContext.HttpContext.Request;
var response = filterContext.HttpContext.Response;
if (MyApp.SiteMinderConfig.Enabled)
{
string[] userRoles = null; // default to null
userRoles = Array.ConvertAll(request.Headers[MyApp.SiteMinderConfig.RolesKey.Value].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries), r => r.Trim());
var identity = new SiteMinderIdentity(request.Headers[MyApp.SiteMinderConfig.UserKey.Value];, "SiteMinder");
if (userRoles != null)
identity.Roles = userRoles.ToList();
var principal = new SiteMinderPrincipal(identity, userRoles);
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
else
{
var roles = Array.ConvertAll(MyApp.SiteMinderConfig.Impersonate.Roles.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries), r => r.Trim());
var identity = new SiteMinderIdentity(MyApp.SiteMinderConfig.Impersonate.Username.Value, "SiteMinder") { Roles = roles.ToList() };
var principal = new SiteMinderPrincipal(identity, roles);
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = principal;
}
}
}
MyApp is a static class that gets initialized at application startup that caches the configuration information so we're not reading it from web.config on every request...
public static class MyApp
{
private static bool _isInitialized;
private static object _lock;
static MyApp()
{
_lock = new object();
}
private static void Initialize()
{
if (!_isInitialized)
{
lock (_lock)
{
if (!_isInitialized)
{
// Initialize application version number
_version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
_siteMinderConfig = (SiteMinderConfiguration)ConfigurationManager.GetSection("siteMinderSecurity");
_isInitialized = true;
}
}
}
}
private static string _version;
public static string Version
{
get
{
Initialize();
return _version;
}
}
private static SiteMinderConfiguration _siteMinderConfig;
public static SiteMinderConfiguration SiteMinderConfig
{
get
{
Initialize();
return _siteMinderConfig;
}
}
}
From what I gather of your situation, you have information in a database that you'll need to lookup based on the information in the headers to get everything you need, so this won't be exactly what you need, but it seems like it should at least get you started.
Hope this helps.

JAXB works with JPA, throw IllegalAnnotationsException

I have been stacked in this place for few days. any help will be appreciated.
Here is my story:
I have a JPA entity class (ExtOffer), and Now I annotate it with JAXB annotation in order to do marshall/unmarshall by JAXB. And I also create a wrapper class(ExtOffers), which basically is a collection of ExtOffer.
And when I call JAXBContext.newInstance(ExtOffers.class), I got an IllegalAnnotationsException:JAXB annotation is placed on a method that is not a JAXB property.
I search google and some post says that it's due to annotate #XmlElement on wrong place.
But my class has #XmlAccessorType(XmlAccessType.NONE) annotation and only the getter method has been annotated with #Xmlelement.
below is my ExtOffer class and ExtOffers class:
//ExtOffer:
#Entity
#Table (name = "extoffer")
#XmlType(name = "ExtOfferType")
#XmlAccessorType(XmlAccessType.NONE)
public class ExtOffer {
public ExtOffer() {
}
#Id
#Column(name = "OfferID", nullable = false, unique = true, length = 32)
protected String offerId;
#Column(name = "HasMoreScreenShot", nullable = false, unique = false, length = 1)
private String hasMoreScreenShot;
public void setOfferId(String offerId) {
this.offerId = offerId;
}
#XmlElement(name="OfferID", required = true)
public String getOfferId() {
return offerId;
}
public void setHasMoreScreenShot(String hasMoreScreenShot) {
this.hasMoreScreenShot= hasMoreScreenShot;
}
#XmlElement(name = "HasMoreScreenShot")
public String GetHasMoreScreenShot() {
return hasMoreScreenShot;
}
}
//ExtOffers wrapper
#XmlRootElement(name="extoffers")
#XmlAccessorType(XmlAccessType.NONE)
public class ExtOfferWrapper {
private List<ExtOffer> extoffers;
public ExtOfferWrapper() {
}
#XmlElement(name="extoffer")
public List<ExtOffer> getExtoffers() {
return extoffers;
}
public void setExtoffers(List<ExtOffer> extoffers) {
this.extoffers = extoffers;
}
}
JAXB annotation is placed on a method that is not a JAXB property
this problem is related to the following location:
at #javax.xml.bind.annotation.XmlElement(nillable=false, name=HasMoreScreenShot, required=false, defaultValue=, type=class javax.xml.bind.annotation.XmlElement$DEFAULT, namespace=##default)
at com.symbio.fuhu.appstore.jpa.entity.ExtOffer
at public java.util.List com.symbio.fuhu.appstore.jaxb.mapping.wrapper.ExtOfferWrapper.getExtoffers()
at com.symbio.fuhu.appstore.jaxb.mapping.wrapper.ExtOfferWrapper
You have an upper case 'G'
#XmlElement(name = "HasMoreScreenShot")
public String GetHasMoreScreenShot() {
return hasMoreScreenShot;
}

In Spring-mvc the attribute names in view have to always match the property names in model?

In the http request body, the way password string is passed is "pass=1111", however in the bean the way password is defined is ''private String password". Is there a way I can use annotation to handle the difference or I have to always match names?
The Http request is like this
curl -H "Accept:text/html" -H "Content-Type application/x-www-form-urlencoded" -d 'email=test%40gmail.com&pass=1111&passconfirm=1111&name=x+y' "http://localhost:8080/project/register"
Handler method is
#RequestMapping(method = RequestMethod.POST, headers = "content-type=application/x-www-form-urlencoded")
public String register(#ModelAttribute UserAccountBean account) ...
UserAccountBean is
public class UserAccountBean2 {
#NotNull
#Size(min = 1, max = 25)
private String name;
#NotNull
#Size(min = 4, max = 8)
private String password;
#NotNull
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String toString() {
return new ToStringCreator(this).append("name", name).append("password", password).toString();
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Use #RequestParam annotation in #InitBinder annotated method, and set the desired value manually.
UserController
#InitBinder(value="user")
public void bind(WebDataBinder dataBinder, WebRequest webRequest, #RequestParam(value="pass", required=false) String password) {
User user = (User) dataBinder.getTarget();
user.setPassword(password);
}
Is there a way I can use annotation to
handle the difference or I have to
always match names?
AFAIK there is no ready-made annotation in Spring MVC that can resolve your problem; you need custom setup to handle the situation.
WebModelAttribute
#Target({ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface WebModelAttribute {
String modelAttributeName();
WebParameterMapping[] parameterMappings();
}
WebParameterMapping
#Target({ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface WebParameterMapping {
String webProperty();
String beanProperty();
}
UserController
#Controller
public class UserController extends AbstractController {
#Override
#InitBinder(value="user")
#WebModelAttribute(modelAttributeName="user", parameterMappings={#WebParameterMapping(webProperty="pass", beanProperty="password")})
protected void bindWebParameters(WebDataBinder dataBinder, WebRequest webRequest, WebParameterResolver mappingResolver) {
super.bindWebParameters(dataBinder, webRequest, mappingResolver);
}
AbstractController
public class AbstractController {
protected void bindWebParameters(WebDataBinder dataBinder, WebRequest webRequest, WebParameterResolver mappingResolver) {
if(mappingResolver != null && dataBinder.getTarget() != null && dataBinder.getObjectName().equals(mappingResolver.getModelAttributeName())) {
String[] allowedFields = mappingResolver.getAllowedFields(dataBinder.getAllowedFields());
String[] disallowedFields = mappingResolver.getDisallowedFields(dataBinder.getDisallowedFields());
dataBinder.setAllowedFields(allowedFields);
dataBinder.setDisallowedFields(disallowedFields);
dataBinder.bind(mappingResolver.getPropertyValues(dataBinder, webRequest));
}
}
}
WebParameterResolver
public class WebParameterResolver {
private String modelAttributeName;
private WebParameterMapping[] parameterMappings;
public WebParameterResolver(String modelAttributeName,
WebParameterMapping[] parameterMappings) {
this.modelAttributeName = modelAttributeName;
this.parameterMappings = parameterMappings;
}
public String getModelAttributeName() {
return modelAttributeName;
}
public String[] getDisallowedFields(String[] existingDisallowedFields) {
List<String> disallowedFields = new ArrayList<String>();
for (WebParameterMapping parameterMapping : parameterMappings) {
disallowedFields.add(parameterMapping.webProperty());
}
if (existingDisallowedFields != null) {
for (String disallowedField : existingDisallowedFields) {
disallowedFields.add(disallowedField);
}
}
return disallowedFields.toArray(new String[disallowedFields.size()]);
}
public String[] getAllowedFields(String[] existingAllowedFields) {
List<String> allowedFields = new ArrayList<String>();
for (WebParameterMapping parameterMapping : parameterMappings) {
allowedFields.add(parameterMapping.beanProperty());
}
if (existingAllowedFields != null) {
for (String allowedField : existingAllowedFields) {
allowedFields.add(allowedField);
}
}
return allowedFields.toArray(new String[allowedFields.size()]);
}
public MutablePropertyValues getPropertyValues(WebDataBinder dataBinder,
WebRequest webRequest) {
MutablePropertyValues propertyValues = new MutablePropertyValues();
for (WebParameterMapping parameterMapping : parameterMappings) {
String[] values = webRequest.getParameterValues(parameterMapping.webProperty());
if (values == null || values.length == 0) {
// do nothing
} else if (values.length == 1) {
propertyValues.add(parameterMapping.beanProperty(), values[0]);
} else {
propertyValues.add(parameterMapping.beanProperty(), values);
}
}
dataBinder.bind(propertyValues);
return propertyValues;
}
}
CustomArgumentResolver
public class CustomArgumentResolver implements WebArgumentResolver {
#Override
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
if(methodParameter.getParameterType().equals(WebParameterResolver.class)) {
WebModelAttribute webModelAttribute = methodParameter.getMethod().getAnnotation(WebModelAttribute.class);
if(webModelAttribute == null) {
throw new RuntimeException("method must have WebModelAttribute");
}
return new WebParameterResolver(webModelAttribute.modelAttributeName(), webModelAttribute.parameterMappings());
}
return UNRESOLVED;
}
}
beans.xml
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolvers" ref="timetracking.annotations.CustomArgumentResolver"/>
</bean>
<bean name="timetracking.annotations.CustomArgumentResolver"
class="timetracking.annotations.CustomArgumentResolver" />
You can also have a public static void bindWebParameters(...) method in some helper class; so you don't have to extend the AbstractController every time.
You can achieve it with this:
#RequestMapping(method = RequestMethod.POST, headers = "content-type=application/x-www-form-urlencoded")
public String register(#ModelAttribute("userAccountBean") UserAccountBean account) ...
#ModelAttribute("userAccountBean")
public UserAccountBean getUserAccountBean(HttpServletRequest req) {
UserAccountBean uab = new UserAccountBean();
uab.setPassword(req.getParameter("pass"));
return uab;
}
There is no annotation based solution in 3.0.
Just provide additional getPass() setPass(String pass) method and you should be set.