58

I have a service defined as follows.

public String getData(@QueryParam("date") Date date)

I'm trying to pass a java.util.Date to it from my client (which is jaxrs:client of CXF, not a generic HTTP client or browser).

My service receives the date as Thu Mar 01 22:33:10 IST 2012 in the HTTP URL. Since CXF won't be able to create a Date object using this String, my client receives a 404 error. I tried using a ParameterHandler on the service side, but I still can't parse it successfully because I'm not expecting the date in any specific format.

As per this post, passing a Date is supposed to work out of the box, but I can't seem to get the basic case working. Am I required to do anything in order to successfully pass a Date object from my client to service? Appreciate any help.

Thanks

Community
  • 1
  • 1
domino
  • 683
  • 2
  • 7
  • 10
  • Not sure what you mean by `results in the date being sent as Thu Mar 01 22:33:10 IST 2012`, since you are *recieving* a date, not *sending* one. Make sure that the URL you are calling is properly encoded before invoking it. – Perception Mar 01 '12 at 17:59
  • Edited the post to clarify. The URL is properly encoded. My client is sending the date parameter as `Thu Mar 01 22:33:10 IST 2012` and my service is also receiving it as-is. However, the date is in a format which can not be parsed (if I use a ParameterHandler) or which can not be used by CXF to directly create a Date object. – domino Mar 01 '12 at 18:34
  • That makes things clearer. Which leads to the next question - is this method expected to also be able to handle dates in other (more standard) formats? – Perception Mar 01 '12 at 18:44
  • Actually, I do not care about the date format at all. It is sufficient as long as I'm able to pass a `Date` object across, the format used internally can be anything. If it helps, I do not mind internally using a specific format (such as yyyyMMddHHmmSS). – domino Mar 01 '12 at 18:51
  • Actually, I was asking in order to better determine a solution to handling the ***incoming*** date. Please see my answer below. – Perception Mar 01 '12 at 19:02

4 Answers4

67

The problem is that JAX-RS dictates that parameter unbundling be done in one of two ways:

  1. The parameter bean has a public constructor that accepts a String
  2. The parameter bean has a static valueOf(String) method.

In your case, the Date is being unbundled via its Date(String) constructor, which cannot handle the input format your client is sending. You have a couple options available to remedy this:


Option 1

Get your client to change the format of the date before they send it. This is the ideal, but probably the hardest to accomplish!


Option 2

Handle the crazy date format. The options for this are:

Change your method signature to accept a string. Attempt to construct a Date object out of that and if that fails, use your own custom SimpleDateFormat class to parse it.

static final DateFormat CRAZY_FORMAT = new SimpleDateFormat("");

public String getData(@QueryParam("date") String dateString) {
    final Date date;
    try {
        date = new Date(dateString); // yes, I know this is a deprecated method
    } catch(Exception e) {
        date = CRAZY_FORMAT.parse(dateString);
    }
}

Define your own parameter class that does the logic mentioned above. Give it a string constructor or static valueOf(String) method that invokes the logic. And an additional method to get the Date when all is said and done.

public class DateParameter implements Serializable {
    public static DateParameter valueOf(String dateString) {
        try {
            date = new Date(dateString); // yes, I know this is a deprecated method
        } catch(Exception e) {
            date = CRAZY_FORMAT.parse(dateString);
        }
    }

    private Date date;
    // Constructor, Getters, Setters
}

public String getData(@QueryParam("date") DateParameter dateParam) {
    final Date date = dateParam.getDate();
}

Or finally, you can register a parameter handler for dates. Where its logic is simply the same as mentioned for the other options above. Note that you need to be using at least CXF 2.5.3 in order to have your parameter handler evaluated before it tries the default unbundling logic.

public class DateHandler implements ParameterHandler<Date> {
    public Map fromString(String s) {
        final Date date;
        try {
            date = new Date(dateString); // yes, I know this is a deprecated method
        } catch(Exception e) {
            date = CRAZY_FORMAT.parse(dateString);
        }
    }
}
Perception
  • 79,279
  • 19
  • 185
  • 195
  • Reg. Option #1: In this case I'm distributing the client myself to users, so I'm free to choose a fixed date format if that helps. But the question is, how do I do this exactly? What determines which format the CXF client would use? I'd still to keep the `java.util.Date` as the `QueryParam` instead of changing to `String` or another `DateParam` like object to encapsulate a `Date` object inside. – domino Mar 01 '12 at 19:39
  • Well if you have control over the client that makes things better. Just pass in any date format that can be handled by the `Date(String)` constructor. An example would be '12/20/2005 09:30:00 +0100' (mm/dd/yyy HH:MM:ss Z). – Perception Mar 01 '12 at 19:55
  • 1
    I do have control over the client, but my signature on both client & service side should be `Date`. Since on the client side, CXF **internally** calls `toString()` on my `Date` object while constructing the query parameters, how do I exercise my control and say use a specific date format and not the default one? Are you suggesting to extend `Date` and override `toString()` (which means the client signature will change)? Or is there any other mechanism which can be used on both client & service side to override the behavior? I guess `MessageBodyReader/Wrtier` is meant only for request body. – domino Mar 02 '12 at 02:25
  • 1
    I have finally used `ParameterHandler` (third approach in Option 2). Since I have control on the client, I am directly parsing using the CRAZY_FORMAT (used by `Date.toString()`). Thanks Perception. – domino Mar 07 '12 at 13:54
  • 7
    Just remember that SimpleDateFormat is NOT thread safe and putting 'static final' is wrong – Nestor Hernandez Loli Oct 28 '14 at 02:11
  • What is the problem in [this](https://i.stack.imgur.com/8ZCXF.png) case? This format supports the Date constructor. – Alex78191 Jan 17 '18 at 19:09
20

Percepiton's answer was very useful, but ParameterHandler has been deprecated in Apache-cxf 3.0, see the Apache-cxf 3.0 Migration Guide:

CXF JAX-RS ParameterHandler has been dropped, please use JAX-RS 2.0 ParamConverterProvider.

So I add an example with the ParamConverterProvider :

public class DateParameterConverterProvider implements ParamConverterProvider {

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> type, Type type1, Annotation[] antns) {
        if (Date.class.equals(type)) {
            @SuppressWarnings("unchecked")
            ParamConverter<T> paramConverter = (ParamConverter<T>) new DateParameterConverter();
            return paramConverter;
        }
        return null;
    }

}

public class DateParameterConverter implements ParamConverter<Date> {

    public static final String format = "yyyy-MM-dd"; // set the format to whatever you need

    @Override
    public Date fromString(String string) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
        try {
            return simpleDateFormat.parse(string);
        } catch (ParseException ex) {
            throw new WebApplicationException(ex);
        }
    }

    @Override
    public String toString(Date t) {
        return new SimpleDateFormat(format).format(t);
    }

}

The @SuppressWarnings is required to suppress an "unchecked or unsafe operations" warning during compilation. See How do I address unchecked cast warnings for more details.

The ParamConverterProvider can be registred as provider. Here is how I did it:

  <jaxrs:server id="myService" address="/rest">
      <jaxrs:serviceBeans>
           ...
      </jaxrs:serviceBeans>

      <jaxrs:providers>
          <ref bean="dateParameterConverterProvider" />
      </jaxrs:providers>
  </jaxrs:server>

  <bean id="dateParameterConverterProvider" class="myPackage.DateParameterConverterProvider"/>

See Apache-cxf JAX-RS : Services Configuration for more information.

Community
  • 1
  • 1
Christophe Weis
  • 2,518
  • 4
  • 28
  • 32
  • Great answer! I use `@Provider` annotation in `DateParameterConverter` instead of xml configuration. It worked for me. – Leib Jan 29 '21 at 10:52
1

Using a custom DateParam class seems the safest option. You can then base your method signatures on that and implement the ugly conversion logic inside the valueOf() method or the class constructor. It is also more self-documenting than using plain strings

Cristian Botiza
  • 419
  • 4
  • 9
0

As @Perception suggests in option two, you can handle the date. But you should use following:

private Date getDateFromString(String dateString) {
    try {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        Date date = df.parse(dateString);
        return date;
    } catch (ParseException e) {
        //WebApplicationException ...("Date format should be yyyy-MM-dd'T'HH:mm:ss", Status.BAD_REQUEST);
    }
}

You call it from within the resource as

Date date = getDateFromString(dateString);//dateString is query param.
kasavbere
  • 5,873
  • 14
  • 49
  • 72