We have rest webservices using spring and cxf. When a checked exception is thrown we get the correct localized exception via soap ui.
But when a unchecked runtime exception is thrown we simply get a stack trace stating the webservice could not loaded.
#XmlType
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement
public class WebServiceExceptionWrapper extends Exception {
/** Type (class name). */
private String type;
/** Message. */
private String message;
/** Stack trace. */
private StackTraceElement[] stackTrace;
/** Cause. */
private Throwable cause;
/** The localized message. */
private String localeErrorCode;
/**
* Construct a wrapper around an exception.
*
* #param e exception
*/
public WebServiceExceptionWrapper(final Exception e) {
this.type = e.getClass().getName();
this.message = StringEscapeUtils.escapeHtml(e.getLocalizedMessage());
this.stackTrace = e.getStackTrace();
this.cause = e.getCause();
if(e instanceof DetailedException) {
this.localeErrorCode = StringEscapeUtils.escapeHtml(((DetailedException) e).getLocaleErrorCode());
}
}
/**
* Gets the type.
*
* #return the type
*/
#XmlElement(nillable = true)
public String getType() {
return this.type;
}
/**
* Sets the type.
*
* #param type the type to set
*/
public void setType(final String type) {
this.type = type;
}
/**
* Gets the message.
*
* #return the message
*/
#XmlElement(nillable = true)
public String getMessage() {
return this.message;
}
/**
* Sets the message.
*
* #param message the message to set
*/
public void setMessage(final String message) {
this.message = message;
}
/**
* Gets the stackTrace.
*
* #return the stackTrace
*/
#XmlElement(nillable = true)
public StackTraceElement[] getStackTrace() {
return this.stackTrace;
}
/**
* Sets the stackTrace.
*
* #param stackTrace the stackTrace to set
*/
public void setStackTrace(final StackTraceElement[] stackTrace) {
this.stackTrace = stackTrace;
}
/**
* Gets the cause.
*
* #return the cause
*/
#XmlElement(nillable = true)
public Throwable getCause() {
return this.cause;
}
/**
* Sets the cause.
*
* #param cause the cause to set
*/
public void setCause(final Throwable cause) {
this.cause = cause;
}
/**
* #return the localeErrorCode
*/
#XmlElement(nillable = true)
public String getLocaleErrorCode() {
return this.localeErrorCode;
}
/**
* #param localeErrorCode the localeErrorCode to set
*/
public void setLocaleErrorCode(final String localeErrorCode) {
this.localeErrorCode = localeErrorCode;
}
How I do prevent a stack trace from being returned. I just want a response code and a reponse message.
The simplest way to handle exception flows in JAX-RS webservices using Apache-CXF is through the javax.ws.rs.ext.ExceptionMapper class marked with an #Provider annotation (or in the Provider section of your jaxrs:server (jaxrs:providers). You can then trap specific exceptions that you want to return specific status codes for instance:
#Provider
public class FooExceptionMapper implements ExceptionMapper<FooException> {
#Override
public Response toResponse(FooException exception) {
... do your work you want to maybe do here
return Response.status(Response.Status.BAD_REQUEST).entity(...whatever...).build();
}
}
Now you create a bean for this mapper (or use annotation driven), tie it to your service method, and then catch your exceptions. In your catch block simply throw this and let the interceptor framework of cxf take over.
Related
How to map avro schema to a LocalDate / LocaldateTime?
The spec says we can use logicalType. But DOES NOT CARE TO SHOW AN EXAMPLE of how it maps to LocalDate in Java. Why is a good example not part of the spec? that is for the specwriters.
Anyay
e.g.
{"namespace": "example.avro",
"type": "record",
"name": "User",
"fields": [
{"name": "date", "type": "int", "logicalType": "date"}
]
}
This maps to int and not to LocalDate e.g,
How to map in avro schema LocalDate?
#org.apache.avro.specific.AvroGenerated
public class User extends org.apache.avro.specific.SpecificRecordBase implements
org.apache.avro.specific.SpecificRecord {
private int date;// This should be LocalDate
}
{
"namespace": "example.avro",
"type": "record",
"name": "User",
"fields": [
{
"name": "date",
"type": {
"type": "int",
"logicalType": "date"
}
}
]
}
//this will generate following mapping to private java.time.LocalDate date;as below with maven-avro-plugin.
/**
* Autogenerated by Avro
*
* DO NOT EDIT DIRECTLY
*/
package example.avro;
import org.apache.avro.generic.GenericArray;
import org.apache.avro.specific.SpecificData;
import org.apache.avro.util.Utf8;
import org.apache.avro.message.BinaryMessageEncoder;
import org.apache.avro.message.BinaryMessageDecoder;
import org.apache.avro.message.SchemaStore;
#org.apache.avro.specific.AvroGenerated
public class User extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
private static final long serialVersionUID = -2875816324033601436L;
public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"User\",\"namespace\":\"example.avro\",\"fields\":[{\"name\":\"date\",\"type\":{\"type\":\"int\",\"logicalType\":\"date\"}}]}");
public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
private static SpecificData MODEL$ = new SpecificData();
static {
MODEL$.addLogicalTypeConversion(new org.apache.avro.data.TimeConversions.DateConversion());
}
private static final BinaryMessageEncoder<User> ENCODER =
new BinaryMessageEncoder<User>(MODEL$, SCHEMA$);
private static final BinaryMessageDecoder<User> DECODER =
new BinaryMessageDecoder<User>(MODEL$, SCHEMA$);
/**
* Return the BinaryMessageEncoder instance used by this class.
* #return the message encoder used by this class
*/
public static BinaryMessageEncoder<User> getEncoder() {
return ENCODER;
}
/**
* Return the BinaryMessageDecoder instance used by this class.
* #return the message decoder used by this class
*/
public static BinaryMessageDecoder<User> getDecoder() {
return DECODER;
}
/**
* Create a new BinaryMessageDecoder instance for this class that uses the specified {#link SchemaStore}.
* #param resolver a {#link SchemaStore} used to find schemas by fingerprint
* #return a BinaryMessageDecoder instance for this class backed by the given SchemaStore
*/
public static BinaryMessageDecoder<User> createDecoder(SchemaStore resolver) {
return new BinaryMessageDecoder<User>(MODEL$, SCHEMA$, resolver);
}
/**
* Serializes this User to a ByteBuffer.
* #return a buffer holding the serialized data for this instance
* #throws java.io.IOException if this instance could not be serialized
*/
public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException {
return ENCODER.encode(this);
}
/**
* Deserializes a User from a ByteBuffer.
* #param b a byte buffer holding serialized data for an instance of this class
* #return a User instance decoded from the given buffer
* #throws java.io.IOException if the given bytes could not be deserialized into an instance of this class
*/
public static User fromByteBuffer(
java.nio.ByteBuffer b) throws java.io.IOException {
return DECODER.decode(b);
}
private java.time.LocalDate date;
/**
* Default constructor. Note that this does not initialize fields
* to their default values from the schema. If that is desired then
* one should use <code>newBuilder()</code>.
*/
public User() {}
/**
* All-args constructor.
* #param date The new value for date
*/
public User(java.time.LocalDate date) {
this.date = date;
}
public org.apache.avro.specific.SpecificData getSpecificData() { return MODEL$; }
public org.apache.avro.Schema getSchema() { return SCHEMA$; }
// Used by DatumWriter. Applications should not call.
public java.lang.Object get(int field$) {
switch (field$) {
case 0: return date;
default: throw new IndexOutOfBoundsException("Invalid index: " + field$);
}
}
private static final org.apache.avro.Conversion<?>[] conversions =
new org.apache.avro.Conversion<?>[] {
new org.apache.avro.data.TimeConversions.DateConversion(),
null
};
#Override
public org.apache.avro.Conversion<?> getConversion(int field) {
return conversions[field];
}
// Used by DatumReader. Applications should not call.
#SuppressWarnings(value="unchecked")
public void put(int field$, java.lang.Object value$) {
switch (field$) {
case 0: date = (java.time.LocalDate)value$; break;
default: throw new IndexOutOfBoundsException("Invalid index: " + field$);
}
}
/**
* Gets the value of the 'date' field.
* #return The value of the 'date' field.
*/
public java.time.LocalDate getDate() {
return date;
}
/**
* Sets the value of the 'date' field.
* #param value the value to set.
*/
public void setDate(java.time.LocalDate value) {
this.date = value;
}
/**
* Creates a new User RecordBuilder.
* #return A new User RecordBuilder
*/
public static example.avro.User.Builder newBuilder() {
return new example.avro.User.Builder();
}
/**
* Creates a new User RecordBuilder by copying an existing Builder.
* #param other The existing builder to copy.
* #return A new User RecordBuilder
*/
public static example.avro.User.Builder newBuilder(example.avro.User.Builder other) {
if (other == null) {
return new example.avro.User.Builder();
} else {
return new example.avro.User.Builder(other);
}
}
/**
* Creates a new User RecordBuilder by copying an existing User instance.
* #param other The existing instance to copy.
* #return A new User RecordBuilder
*/
public static example.avro.User.Builder newBuilder(example.avro.User other) {
if (other == null) {
return new example.avro.User.Builder();
} else {
return new example.avro.User.Builder(other);
}
}
/**
* RecordBuilder for User instances.
*/
#org.apache.avro.specific.AvroGenerated
public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase<User>
implements org.apache.avro.data.RecordBuilder<User> {
private java.time.LocalDate date;
/** Creates a new Builder */
private Builder() {
super(SCHEMA$);
}
/**
* Creates a Builder by copying an existing Builder.
* #param other The existing Builder to copy.
*/
private Builder(example.avro.User.Builder other) {
super(other);
if (isValidValue(fields()[0], other.date)) {
this.date = data().deepCopy(fields()[0].schema(), other.date);
fieldSetFlags()[0] = other.fieldSetFlags()[0];
}
}
/**
* Creates a Builder by copying an existing User instance
* #param other The existing instance to copy.
*/
private Builder(example.avro.User other) {
super(SCHEMA$);
if (isValidValue(fields()[0], other.date)) {
this.date = data().deepCopy(fields()[0].schema(), other.date);
fieldSetFlags()[0] = true;
}
}
/**
* Gets the value of the 'date' field.
* #return The value.
*/
public java.time.LocalDate getDate() {
return date;
}
/**
* Sets the value of the 'date' field.
* #param value The value of 'date'.
* #return This builder.
*/
public example.avro.User.Builder setDate(java.time.LocalDate value) {
validate(fields()[0], value);
this.date = value;
fieldSetFlags()[0] = true;
return this;
}
/**
* Checks whether the 'date' field has been set.
* #return True if the 'date' field has been set, false otherwise.
*/
public boolean hasDate() {
return fieldSetFlags()[0];
}
/**
* Clears the value of the 'date' field.
* #return This builder.
*/
public example.avro.User.Builder clearDate() {
fieldSetFlags()[0] = false;
return this;
}
#Override
#SuppressWarnings("unchecked")
public User build() {
try {
User record = new User();
record.date = fieldSetFlags()[0] ? this.date : (java.time.LocalDate) defaultValue(fields()[0]);
return record;
} catch (org.apache.avro.AvroMissingFieldException e) {
throw e;
} catch (java.lang.Exception e) {
throw new org.apache.avro.AvroRuntimeException(e);
}
}
}
#SuppressWarnings("unchecked")
private static final org.apache.avro.io.DatumWriter<User>
WRITER$ = (org.apache.avro.io.DatumWriter<User>)MODEL$.createDatumWriter(SCHEMA$);
#Override public void writeExternal(java.io.ObjectOutput out)
throws java.io.IOException {
WRITER$.write(this, SpecificData.getEncoder(out));
}
#SuppressWarnings("unchecked")
private static final org.apache.avro.io.DatumReader<User>
READER$ = (org.apache.avro.io.DatumReader<User>)MODEL$.createDatumReader(SCHEMA$);
#Override public void readExternal(java.io.ObjectInput in)
throws java.io.IOException {
READER$.read(this, SpecificData.getDecoder(in));
}
}
In a Spring Webflux application, I have the below "AuthenticationManager" implementation. On a high level, below is what it is trying to do -
Certain url paths are configured in prop file as requiring basic auth while others require bearer token.
For basic auth, BcryptPasswordEncoder.matches method is used to compare incoming password with encoded one stored in prop file.
For bearer token auth, io.jsonwebtoken library is being used to validate token.
#Component
public class AuthenticationManager implements ReactiveAuthenticationManager {
/** The jwt util. */
#Autowired
private JWTUtil jwtUtil;
#Override
public Mono<Authentication> authenticate(Authentication authobj) {
return Mono.just(authobj).flatMap(authentication -> {
String credentials = authentication.getCredentials().toString();
String path = authentication.getPrincipal().toString();
BasicAuthPath basicAuthPath = jwtUtil.isBasicAuthUrl(path);
if (jwtUtil.isBasicScheme(credentials) || jwtUtil.isBearerScheme(credentials)) {
if (jwtUtil.isBasicScheme(credentials) && basicAuthPath.isState()) {
credentials = credentials.substring(5);
if (jwtUtil.isBasicAuthSuccess(credentials.trim(), basicAuthPath.getPath())) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(credentials, null, null);
return Mono.just(auth);
} else {
return Mono.empty();
}
} else if (jwtUtil.isBearerTokenAuthUrl(path) && jwtUtil.isBearerScheme(credentials)) {
credentials = credentials.substring(7);
String username;
try {
username = jwtUtil.getUsernameFromToken(credentials);
} catch (Exception e) {
username = null;
}
if (jwtUtil.validateToken(credentials)) {
Claims claims = jwtUtil.getAllClaimsFromToken(credentials);
List<String> rolesMap = claims.get("role", List.class);
List<Role> roles = new ArrayList<>();
for (String rolemap : rolesMap) {
roles.add(Role.valueOf(rolemap));
}
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null,
roles.stream().map(authority -> new SimpleGrantedAuthority(authority.name())).collect(Collectors.toList()));
return Mono.just(auth);
} else {
return Mono.empty();
}
} else {
return Mono.empty();
}
}
return Mono.empty();
});
}
}
JwtUtil Code below
#Component
#Slf4j
public class JWTUtil implements Serializable {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The env. */
#Autowired
private Environment env;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private JwtParser jwtParser;
#Autowired
private Key signingKey;
private static final PathPatternParser DEFAULT_PATTERN_PARSER = new PathPatternParser();
/**
* Checks if is basic auth success.
*
* #param inputCredentials the input credentials
* #param path the path
* #return true, if is basic auth success
*/
public boolean isBasicAuthSuccess(String inputCredentials, String path) {
String pathtoprop = pathtoprop(path);
String configpwd = env.getProperty(pathtoprop);
if (StringUtils.isBlank(configpwd)) {
configpwd = env.getProperty(pathtoprop.substring(0, pathtoprop.lastIndexOf(".")));
}
return passwordEncoder.matches(new String(Base64.getDecoder().decode(inputCredentials)), configpwd);
//return true;
}
/**
* Checks if is basic auth url.
*
* #param path the path
* #return true, if is basic auth url
*/
public BasicAuthPath isBasicAuthUrl(String path) {
BasicAuthPath basicAuthPath = new BasicAuthPath();
if (StringUtils.isNotBlank(env.getProperty("gateway.basicauth.urls"))) {
return pathMatcherBasic(StringUtils.split(env.getProperty("gateway.basicauth.urls"), ","), path);
}
basicAuthPath.setState(false);
return basicAuthPath;
}
/**
* pathMatcher
*
* #param pathPatterns
* #param path
* #return
*/
private BasicAuthPath pathMatcherBasic(String[] pathPatterns, String path) {
BasicAuthPath basicAuthPath = new BasicAuthPath();
for (String pathPattern : pathPatterns) {
PathPattern pattern = DEFAULT_PATTERN_PARSER.parse(pathPattern);
if (pattern.matches(PathContainer.parsePath(path))) {
basicAuthPath.setState(true);
basicAuthPath.setPath(pathPattern);
return basicAuthPath;
}
}
basicAuthPath.setState(false);
return basicAuthPath;
}
/**
* Checks if is bearer token auth url.
*
* #param path the path
* #return true, if is bearer token auth url
*/
public boolean isBearerTokenAuthUrl(String path) {
if (StringUtils.isNotBlank(env.getProperty("gateway.bearertoken.urls"))
&& pathMatcher(StringUtils.split(env.getProperty("gateway.bearertoken.urls"), ","), path)) {
return true;
}
return false;
}
/**
* pathMatcher
*
* #param pathPatterns
* #param path
* #return
*/
private boolean pathMatcher(String[] pathPatterns, String path) {
for (String pathPattern : pathPatterns) {
PathPattern pattern = DEFAULT_PATTERN_PARSER.parse(pathPattern);
if (pattern.matches(PathContainer.parsePath(path))) {
return true;
}
}
return false;
}
/**
* isBasicScheme
*
* #param credentials
* #return
*/
public boolean isBasicScheme(String credentials) {
if (StringUtils.isNotBlank(credentials) && credentials.startsWith(RestUriConstants.BASIC_TOKE_PREFIX)) {
return true;
}
return false;
}
/**
* isBearerScheme
*
* #param credentials
* #return
*/
public boolean isBearerScheme(String credentials) {
if (StringUtils.isNotBlank(credentials) && credentials.startsWith(RestUriConstants.BEARER_TOKE_PREFIX)) {
return true;
}
return false;
}
/**
* Gets the all claims from token.
*
* #param token the token
* #return the all claims from token
*/
public Claims getAllClaimsFromToken(String token) {
return jwtParser.parseClaimsJws(token).getBody();
}
/**
* Gets the username from token.
*
* #param token the token
* #return the username from token
*/
public String getUsernameFromToken(String token) {
return getAllClaimsFromToken(token).getSubject();
}
/**
* Gets the expiration date from token.
*
* #param token the token
* #return the expiration date from token
*/
public Date getExpirationDateFromToken(String token) {
return getAllClaimsFromToken(token).getExpiration();
}
/**
* Checks if is token expired.
*
* #param token the token
* #return the boolean
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* Generate token.
*
* #param user the user
* #return the string
*/
public String generateToken(User user) {
Map<String, Object> claims = new HashMap<>();
claims.put("role", user.getRoles());
return doGenerateToken(claims, user.getUsername());
}
/**
* Do generate token.
*
* #param claims the claims
* #param username the username
* #return the string
*/
private String doGenerateToken(Map<String, Object> claims, String username) {
Long expirationTimeLong = Long.parseLong(env.getProperty("gateway.apollo.token.encryption.exp")); // in second
final Date createdDate = new Date();
final Date expirationDate = new Date(createdDate.getTime() + expirationTimeLong * 1000);
return Jwts.builder().setClaims(claims).setSubject(username).setIssuedAt(createdDate).setExpiration(expirationDate).signWith(signingKey)
.compact();
}
/**
* Validate token.
*
* #param token the token
* #return the boolean
*/
public Boolean validateToken(String token) {
Boolean result = false;
try {
result = !isTokenExpired(token);
} catch (Exception e) {
log.error("Exception in validateToken - {}",token);
}
return result;
}
private String pathtoprop(String path) {
return "gateway.basicauth.credentials" + path.replace('/', '.');
}
}
During PST done with high number of parallel threads, high cpu utilization is being observed. I think it may be because a lot of the above code is blocking. If that is true, could anyone help with providing pointers on how to refactor this code in a reactive way? Should I be using a thread pool like below for better performance -
Mono.just(authobj).publishOn(Schedulers.newParallel("password-encoder", Schedulers.DEFAULT_POOL_SIZE, true);).......
I have something similar, except I am using Jose4J. What you have isn't technically blocking code but the nature of JWT signatures is CPU intensive.
What I do though is publishOn another scheduler so the rest of the gateway can still do the processing of other calls that do not involve signing.
I would also recommend adding a .timeout on the method that is being called by the user so that it will free up the thread soon in case it gets overloaded. You can add Resilience4J TimeLimitter as well, but that may be overkill.
I've got an entity I send in a datatable (webix) thanks to an API rest. In this entity I have several ManyToOne relationeships which I don't know how to display or edit.
In my datatable, when I try to display the field which is in a relathinship I've got this instead of the id
[object Object]
When I try to edit this field by adding the id I've got this error message
Type error: Argument 1 passed to ErpBundle\Entity\Trial::setGSponsor() must be an instance of ErpBundle\Entity\Sponsor, string given
I understand what I get in my datatable is an object but I don't know how to get the Id of the name instead (and being able to chose it from the datatable).
Do I have to edit the getter? The repository?
I've tried to edit my getter like that but of course it's not working:
/**
* Get gSponsor
*
* #return \ErpBundle\Entity\Sponsor
*/
public function getGSponsor()
{
return $this->gSponsor->getId();
}
Below a part of my code:
In the controller, I'm just rendering the template for the view. Webix just take the url as an argument to get the data (url: "rest->{{ path('erp_trialapi_cgetload') }}") The datatable is displayed automatically from what I send in my ApiController
Part of my entity
class Trial
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\ManyToOne(targetEntity="ErpBundle\Entity\Sponsor")
*/
private $gSponsor;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set gSponsor
*
* #param \ErpBundle\Entity\Sponsor $gSponsor
*
* #return Trial
*/
public function setGSponsor(\ErpBundle\Entity\Sponsor $gSponsor = null)
{
$this->gSponsor->get = $gSponsor;
return $this;
}
/**
* Get gSponsor
*
* #return \ErpBundle\Entity\Sponsor
*/
public function getGSponsor()
{
return $this->gSponsor;
}
My API
class TrialApiController extends FOSRestController
{
/**
* #Rest\Get("/api_t/get")
*/
public function cgetLoadAction()
{
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('ErpBundle:Trial');
$trials = $repo->findAll();
return $trials;
}
I'd appreciate any kind of help.
Thank you very much!
I use GWT 2.6.1 with GWTP-Rest Api, when i compile, i get an error.
My class Page implements Iterable & Serializable. This class come from the spring framework but was adaptable to GWT (this class is use by an other project which works with RPC. The error comes from an other class "Sort" which implements the same Interface. See code below.
Error come from com.github.nmorel.gwtjackson.rebind.ObjectMapperGenerator which create an exception.
if I delete the Iterator implementation of OrderMobile, no error occure.
Error in console :
Creating deserializer for **.***.**.**.**.***.*****.Page
[INFO] [ERROR] Wrong number of argument for a java.lang.Iterable implementation
Code from Sort :
public class SortMobile implements Serializable, Iterable<OrderMobile>
{
private static final long serialVersionUID = -8226494389482286795L;
public static final Direction DEFAULT_DIRECTION = Direction.ASC;
private List<OrderMobile> orders = new ArrayList<OrderMobile>();
SortMobile() {
}
/**
* Creates a new {#link Sort} instance using the given {#link Order}s.
*
* #param orders must not be {#literal null}.
*/
public SortMobile(final OrderMobile... orders) {
this(Arrays.asList(orders));
}
/**
* Creates a new {#link SortMobile} instance.
*
* #param list must not be {#literal null} or contain {#literal null}.
*/
public SortMobile(final List<OrderMobile> list) {
if (null == list || list.isEmpty()) {
throw new IllegalArgumentException("You have to provide at least one sort property to sort by!");
}
this.orders = list;
}
/**
* Creates a new {#link SortMobile} instance. Order defaults to {#value Direction#ASC}.
*
* #param properties must not be {#literal null} or contain {#literal null} or empty strings
*/
public SortMobile(final String... properties) {
this(DEFAULT_DIRECTION, properties);
}
/**
* Creates a new {#link SortMobile} instance.
*
* #param direction defaults to {#linke Sort#DEFAULT_DIRECTION} (for {#literal null} cases, too)
* #param properties must not be {#literal null} or contain {#literal null} or empty strings
*/
public SortMobile(final Direction direction, final String... properties) {
this(direction, properties == null ? new ArrayList<String>() : Arrays.asList(properties));
}
/**
* Creates a new {#link SortMobile} instance.
*
* #param direction
* #param properties
*/
public SortMobile(final Direction direction, final List<String> properties) {
if (properties == null || properties.isEmpty()) {
throw new IllegalArgumentException("You have to provide at least one property to sort by!");
}
this.orders = new ArrayList<OrderMobile>(properties.size());
for (String property : properties) {
this.orders.add(new OrderMobile(direction, property));
}
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#equals(java.lang.Object)
*/
#Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SortMobile)) {
return false;
}
SortMobile that = (SortMobile) obj;
return this.orders.equals(that.orders);
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#hashCode()
*/
#Override
public int hashCode() {
int result = 17;
result = 31 * result + orders.hashCode();
return result;
}
// GETTER SETTER
public void setOrders(final List<OrderMobile> orders) {
this.orders = orders;
}
public List<OrderMobile> getOrders() {
return orders;
}
public static Direction getDefaultDirection() {
return DEFAULT_DIRECTION;
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#toString()
*/
#Override
public String toString() {
return Arrays.toString(orders.toArray(new OrderMobile[orders.size()]));
}
// ### elements of iterator implementation
#Override
public Iterator<OrderMobile> iterator() {
return this.orders.iterator();
}
/**
* Returns a new {#link Sort} consisting of the {#link Order}s of the current {#link Sort} combined with the given
* ones.
*
* #param sort can be {#literal null}.
* #return
*/
public SortMobile and(final SortMobile sort) {
if (sort == null) {
return this;
}
ArrayList<OrderMobile> these = new ArrayList<OrderMobile>(this.orders);
for (OrderMobile order : sort) {
these.add(order);
}
return new SortMobile(these);
}
/**
* Returns the order registered for the given property.
*
* #param property
* #return
*/
public OrderMobile getOrderFor(final String property) {
for (OrderMobile order : this) {
if (order.getProperty().equals(property)) {
return order;
}
}
return null;
}
}
OrderMobile is a simple dto :
public class OrderMobile implements Serializable {
private static final long serialVersionUID = 1522511010900108987L;
private static final Direction DEFAULT_DIRECTION = Direction.ASC;
private Direction direction;
private String property;
OrderMobile() {
super();
}
/**
* Creates a new {#link OrderMobile} instance. if order is {#literal null} then order defaults to
* {#link SortMobile#DEFAULT_DIRECTION}
*
* #param direction can be {#literal null}, will default to {#link SortMobile#DEFAULT_DIRECTION}
* #param property must not be {#literal null} or empty.
*/
public OrderMobile(final Direction direction, final String property) {
if (!hasText(property)) {
throw new IllegalArgumentException("Property must not null or empty!");
}
this.direction = direction == null ? DEFAULT_DIRECTION : direction;
this.property = property;
}
private boolean hasText(final String s) {
return s != null && s.trim().length() > 0;
}
/**
* Creates a new {#link OrderMobile} instance. Takes a single property. Direction defaults to
* {#link SortMobile#DEFAULT_DIRECTION}.
*
* #param property must not be {#literal null} or empty.
*/
public OrderMobile(final String property) {
this(DEFAULT_DIRECTION, property);
}
/**
* Returns the order the property shall be sorted for.
*
* #return
*/
public Direction getDirection() {
return direction;
}
public void setDirection(final Direction direction) {
this.direction = direction;
}
/**
* Returns the property to order for.
*
* #return
*/
public String getProperty() {
return property;
}
public void setProperty(final String property) {
this.property = property;
}
/**
* Returns whether sorting for this property shall be ascending.
*
* #return
*/
public boolean isAscending() {
return this.direction.equals(Direction.ASC);
}
/**
* Returns a new {#link OrderMobile} with the given {#link OrderMobile}.
*
* #param order
* #return
*/
public OrderMobile with(final Direction order) {
return new OrderMobile(order, this.property);
}
/**
* Returns a new {#link SortMobile} instance for the given properties.
*
* #param properties
* #return
*/
public SortMobile withProperties(final String... properties) {
return new SortMobile(this.direction, properties);
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#hashCode()
*/
#Override
public int hashCode() {
int result = 17;
result = 31 * result + direction.hashCode();
result = 31 * result + property.hashCode();
return result;
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#equals(java.lang.Object)
*/
#Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof OrderMobile)) {
return false;
}
OrderMobile that = (OrderMobile) obj;
return this.direction.equals(that.direction) && this.property.equals(that.property);
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#toString()
*/
#Override
public String toString() {
return property + ":" + direction;
}
}
3.Code of Page :
public class PageMobile<T> implements Iterable<T>, Serializable {
private static final long serialVersionUID = 867755909294344406L;
private List<T> content = new ArrayList<T>();
private PageRequestMobile pageable;
private long total;
// for serialization
PageMobile() {
}
/**
* Constructor of {#code Page}.
*
* #param content the content of this page, must not be {#literal null}.
* #param pageable the paging information, can be {#literal null}.
* #param total the total amount of items available
*/
public PageMobile(final List<T> content, final PageRequestMobile pageable, final long total) {
if (null == content) {
throw new IllegalArgumentException("Content must not be null!");
}
this.content.addAll(content);
this.total = total;
this.pageable = pageable;
}
/**
* Creates a new {#link PageMobile} with the given content. This will result in the created {#link PageMobile} being
* identical
* to the entire {#link List}.
*
* #param content must not be {#literal null}.
*/
public PageMobile(final List<T> content) {
this(content, null, null == content ? 0 : content.size());
}
/**
* Returns the number of the current page. Is always non-negative and less that {#code Page#getTotalPages()}.
*
* #return the number of the current page
*/
public int getNumber() {
return pageable == null ? 0 : pageable.getPageNumber();
}
/**
* Returns the size of the page.
*
* #return the size of the page
*/
public int getSize() {
return pageable == null ? 0 : pageable.getPageSize();
}
/*
* (non-Javadoc)
*
* #see org.springframework.data.domain.Page#getTotalPages()
*/
public int getTotalPages() {
return getSize() == 0 ? 0 : (int) Math.ceil((double) total / (double) getSize());
}
/**
* Returns the number of elements currently on this page.
*
* #return the number of elements currently on this page
*/
public int getNumberOfElements() {
return content.size();
}
/**
* Returns the total amount of elements.
*
* #return the total amount of elements
*/
public long getTotalElements() {
return total;
}
/**
* set the total amount of elements.
*
*
*/
public void setTotalElements(final Long total) {
this.total = total;
}
/*
* (non-Javadoc)
*
* #see org.springframework.data.domain.Page#hasPreviousPage()
*/
public boolean hasPreviousPage() {
return getNumber() > 0;
}
/*
* (non-Javadoc)
*
* #see org.springframework.data.domain.Page#isFirstPage()
*/
public boolean isFirstPage() {
return !hasPreviousPage();
}
/*
* (non-Javadoc)
*
* #see org.springframework.data.domain.Page#hasNextPage()
*/
public boolean hasNextPage() {
return (getNumber() + 1) * getSize() < total;
}
/*
* (non-Javadoc)
*
* #see org.springframework.data.domain.Page#isLastPage()
*/
public boolean isLastPage() {
return !hasNextPage();
}
/**
* Returns the page content as {#link List}.
*
* #return
*/
public List<T> getContent() {
return Collections.unmodifiableList(content);
}
/*
* (non-Javadoc)
*
* #see org.springframework.data.domain.Page#hasContent()
*/
public boolean hasContent() {
return !content.isEmpty();
}
/*
* (non-Javadoc)
*
* #see org.springframework.data.domain.Page#getSort()
*/
// FIXME to the version in
public SortMobile getSort() {
return pageable == null ? null : pageable.getSort();
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#toString()
*/
#Override
public String toString() {
String contentType = "UNKNOWN";
if (content.size() > 0) {
contentType = content.get(0).getClass().getName();
}
return "Page " + getNumber() + " of " + getTotalPages() + " containing " + contentType + " instances";
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#equals(java.lang.Object)
*/
#Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PageMobile<?>)) {
return false;
}
PageMobile<?> that = (PageMobile<?>) obj;
boolean totalEqual = this.total == that.total;
boolean contentEqual = this.content.equals(that.content);
boolean pageableEqual = this.pageable == null ? that.pageable == null : this.pageable.equals(that.pageable);
return totalEqual && contentEqual && pageableEqual;
}
/*
* (non-Javadoc)
*
* #see java.lang.Object#hashCode()
*/
#Override
public int hashCode() {
int result = 17;
result = 31 * result + (int) (total ^ total >>> 32);
result = 31 * result + (pageable == null ? 0 : pageable.hashCode());
result = 31 * result + content.hashCode();
return result;
}
/**
* Adjust the page size, distributing items equally (+/- 1 item) on each page, given a maximum page size.
*
* WARNING: this function reverse pages order.
* TODO: delegate page order reversing to another function, to limit a single behavior for a given function.
*
* #param page The page
* #param request The associated page request
* #param maxPageSize The maximum page size
* #return The new ajusted page
*/
public static <T> PageMobile<T> adjustPageSize(final PageMobile<T> page, final PageRequestMobile request, final int maxPageSize) {
int totalElnts = (int) page.getTotalElements();
int currentPageNumber = request.getPageNumber();
if (totalElnts == 0) {
List<T> newContent = new ArrayList<T>(0);
PageRequestMobile newRequest = new PageRequestMobile(currentPageNumber, maxPageSize);
return new PageMobile<T>(newContent, newRequest, totalElnts);
}
int nbPages = (int) Math.ceil((double) totalElnts / maxPageSize);
int pageSize = totalElnts / nbPages;
int nbOrphans = totalElnts % nbPages;
if (nbOrphans > 0) {
++pageSize;
}
int startIndex = totalElnts;
int endIndex = totalElnts;
for (int pageNumber = 0; pageNumber <= currentPageNumber; ++pageNumber) {
int currentPageSize = nbOrphans == 0 || pageNumber < nbOrphans ? pageSize : pageSize - 1;
startIndex -= currentPageSize;
endIndex = startIndex + currentPageSize;
}
List<T> newContent = page.getContent().subList(startIndex, endIndex);
PageRequestMobile newRequest = new PageRequestMobile(currentPageNumber, pageSize);
return new PageMobile<T>(newContent, newRequest, totalElnts);
}
// GETTER SETTER
public void setContent(final List<T> content) {
this.content = content;
}
public void setPageable(final PageRequestMobile pageable) {
this.pageable = pageable;
}
public PageRequestMobile getPageable() {
return pageable;
}
public long getTotal() {
return total;
}
public void setTotal(final long total)
{
this.total = total;
}
#Override
public Iterator<T> iterator() {
return this.content.iterator();
}
}
Wrapper to response :
public class Result<T> {
protected Boolean error;
protected String errorMessage;
private HashMap<String, String> errorDetails;
private T objectReceive;
public Result()
{
}
/**
* Initialize with the error at true
*
* #param objectReceive : object to transmit
* #param messageError : message error
*/
public Result(final T objectReceive, final String messageError)
{
this.objectReceive = objectReceive;
hasError(messageError);
}
/**
* Initialize with the error at false
*
* #param objectReceive : object to transmit
*/
public Result(final T objectReceive)
{
this.objectReceive = objectReceive;
this.error = false;
}
public void hasError(final String errorMessage)
{
this.errorDetails = new HashMap<String, String>();
this.error = false;
if (errorMessage != null)
{
this.error = true;
this.errorMessage = errorMessage;
}
}
public Result(final String errorMessage)
{
this.errorDetails = new HashMap<String, String>();
this.error = false;
if (errorMessage != null)
{
this.error = true;
this.errorMessage = errorMessage;
}
}
public Result<T> addDetail(final String name, final String value) {
errorDetails.put(name, value);
return this;
}
public Boolean getError() {
return error;
}
public String getErrorMessage() {
return errorMessage;
}
public HashMap<String, String> getErrorDetails() {
return errorDetails;
}
public void setError(final Boolean error) {
this.error = error;
}
public void setErrorDetails(final HashMap<String, String> errorDetails) {
this.errorDetails = errorDetails;
}
public void setErrorMessage(final String errorMessage) {
this.errorMessage = errorMessage;
}
public T getObjectReceive() {
return objectReceive;
}
public void setObjectReceive(final T objectReceive) {
this.objectReceive = objectReceive;
}
}
With the current version of gwt-jackson (0.6.1), what is sure, you won't be able to use SortMobile as a property in a class. I'll look how I can fix it.
gwt-jackson fails to parse SortMobile because it implements directly Iterable<OrderMobile> and not Iterable<T> with SortMobile<T>.
As a workaround, you have a few solutions :
you can declare the property as a Iterable<OrderMobile> in Page. This way, gwt-jackson will use the serializer/deserializer for Iterable but the instance created by the deserializer will be an ArrayList.
change SortMobile to SortMobile<T extends OrderMobile> implements Iterable<T>
declare your own serializer/deserializer for SortMobile by following the wiki.
Edit :
Since version 0.6.2, you should be able to use SortMobile without compilation error.
I'm having troubles displaying a dropdown list with the proper values. I'm using the <spring-form:select>, <spring-form:options> and <spring-form:option> tags, and I just can't get it to display the correct options. With the following code, I should only be listing "Option 2", "Option 7", and "Option 8".
*Note - I do NOT want to display every possible Enum value, but for some reason Spring seems to want to display them all. It appears to be completely ignoring the list that is provided to the <spring-form:options> tag.
JSP Tags
<spring-form:select path="selectOptions">
<spring-form:option value="" label="*** Select Option ***" />
<spring-form:options path="${availableOptions}" />
</spring-form:select>
Enum
public enum SelectOptions {
// CHECKSTYLE_OFF: LineLength
/**
* Option 1.
*/
OPTION_1(1, "Option 1"),
/**
* Option 2.
*/
OPTION_2(2, "Option 2"),
/**
* Option 3.
*/
OPTION_3(3, "Option 3"),
/**
* Option 4.
*/
OPTION_4(4, "Option 4"),
/**
* Option 5.
*/
OPTION_5(5, "Option 5"),
/**
* Option 6.
*/
OPTION_6(6, "Option 6"),
/**
* Option 7.
*/
OPTION_7(7, "Option 7"),
/**
* Option 8.
*/
OPTION_8(8, "Option 8"),
/**
* Option 9.
*/
OPTION_9(9, "Option 9"),
/**
* Option 10.
*/
OPTION_10(10, "Option 10");
private int id;
private String description;
/**
* Constructor.
*
* #param id the id
* #param description the description
*/
private SelectOptions(final int id, final String description) {
this.id = id;
this.description = description;
}
/**
* Retrieves the {#link SelectOptions} associated with the passed in id.
*
* #param id the id associated with the SelectOptions
* #return the SelectOptions
*/
public static SelectOptions getInstance(final int id) {
for (final SelectOptions selectOptions : SelectOptions.values()) {
if (selectOptions.id == id) {
return selectOptions;
}
}
throw new IllegalArgumentException("SelectOptions could not be determined with id [" + id + "]");
}
/**
* Retrieves the {#link SelectOptions} associated with the passed in description.
*
* #param description the description associated with the SelectOptions
* #return the SelectOptions
*/
public static SelectOptions getInstance(final String description) {
for (final SelectOptions selectOptions : SelectOptions.values()) {
if (selectOptions.description == description) {
return selectOptions;
}
}
throw new IllegalArgumentException("SelectOptions could not be determined with description [" + description + "]");
}
/**
* Simple Getter.
*
* #return the id
*/
public int getId() {
return this.id;
}
/**
* Simple Setter.
*
* #param id the id to set
*/
public void setId(final int id) {
this.id = id;
}
/**
* Simple Getter.
*
* #return the description
*/
public String getDescription() {
return this.description;
}
/**
* Simple Setter.
*
* #param description the description to set
*/
public void setDescription(final String description) {
this.description = description;
}
}
Controller
public class SpringController implements SpringControllerInterface {
/**
* /WEB-INF/jsp/myJSP.jsp.
*/
private static final String PAGE = "/WEB-INF/jsp/myJSP.jsp";
/**
* {#inheritDoc}
*/
#Override
public ModelAndView load(final Model model) {
final ModelAndView mav = new ModelAndView(PAGE);
final List<SelectOptions> availableOptions = this.getAvailableOptions();
mav.addObject("availableOptions", availableOptions);
return mav;
}
/**
* {#inheritDoc}
*/
#Override
public ModelAndView save(final Model model) {
final ModelAndView mav = new ModelAndView(PAGE);
// TODO
return mav;
}
/**
* {#inheritDoc}
*/
#Override
public Model getModel() {
return new ModelImpl();
}
/**
* #return a list of available options
*/
public List<SelectOptions> getAvailableOptions() {
final List<SelectOptions> availableOptions = Lists.newArrayList();
availableOptions.add(SelectOptions.OPTION_1);
availableOptions.add(SelectOptions.OPTION_7);
availableOptions.add(SelectOptions.OPTION_8);
return availableOptions;
}
}
Model
public class ModelImpl implements Model {
private SelectOptions selectOptions;
/**
* Simple Getter.
*
* #return the selectOptions
*/
#Override
public SelectOptions getSelectOptions() {
return this.selectOptions;
}
/**
* Simple Setter.
*
* #param selectOptions the selectOptions to set
*/
#Override
public void setSelectOptions(final SelectOptions selectOptions) {
this.selectOptions = selectOptions;
}
}
If you created a spring controller and you want to pass an enum to your jsp page, you can do it like this:
Enum example:
public enum Coin {
HEADS("Heads", "heads"),
TAILS("Tails", "tails");
private final String fullName;
private final String shortName;
private Coin(String fullName, String shortName) {
this.fullName = fullName;
this.shortName = shortName;
}
public String getFullName() {
return fullName;
}
public String getShortName() {
return shortName;
}
}
Passing this enum to your model:
model.addObject("coins", Coin.values());
Using it in your jsp page with form:
<form:select path="selection">
<form:options items="${coins}" itemValue="shortName" itemLabel="fullName" />
</form:select>
It looks like the solution to this problem was that I was using the attribute "path" in the <spring-form:options> tag. It should have been "items", not "path".
the corrected JSP fragment:
<spring-form:select path="selectOptions">
<spring-form:option value="" label="*** Select Option ***" />
<spring-form:options items="${availableOptions}" />
</spring-form:select>
You do not even need to use items attribute if you are using <spring-form:options> tag.
The options tag:
Renders a list of HTML 'option' tags. Sets 'selected' as appropriate based on bound value.
References Spring form tld.
So for your need I guess falling back to JSTL core with <spring-form:option/> will suffice.