Статьи

Java REST JAX-RS 2.0 — как обрабатывать типы данных даты, времени и метки времени

Будь то отправка HTTP-сообщения X-Form-Urlencoded или JSON в конечную точку ресурса REST, для данных, относящихся к дате или времени, не существует определенного «типа данных». Большинство разработчиков будут публиковать эти данные как «String» или просто конвертировать их в значение метки времени Unix (например, 1435061152). Но по мере того, как разработчики внедряют все больше и больше методов конечной точки, коды для анализа значений представления строки даты, времени и метки времени в реальных java.sql.Date или java.util.Date будут повторяться (и скучно). Итак, цель этой статьи — показать, как реализовать пользовательский тип данных для обработки строковых значений, связанных с датой и временем, в параметрах метода конечной точки REST JAX-RS 2.0.

Совместимость

Коды были протестированы с Payara 4.1 и Wildfly 8.2. Для остальных серверов приложений и контейнеров сервлетов для этого требуется совместимость библиотек JAX-RS 2.0 / Java EE 7.

Образец заявки

Чтобы продемонстрировать это, давайте создадим пример приложения, которое имеет конечную точку ресурса JAX-RS REST, которая принимает классы объектов пользовательских типов данных через значения параметров @FormParam и преобразует их в java.sql.Date , java.sql.Time , java. sql.Timestamp и java.util.Date для удобства.

Пример HTTP-запроса POST

Скажем, HTTP POST из приведенного ниже URL сделан (с SampleApplication в качестве имени приложения и, следовательно, контекста):

Http: // <имя хоста>: <порт> / SampleApplication / отдых-апи / запрос обработчика / пост-запрос-с даты-времени и /

Что касается параметров HTTP, которые будут опубликованы вместе с этим URL, они:

Параметры сообщения Значение (Строка) Шаблон SimpleDateFormat Пользовательский тип данных Имя класса
date_field 1948-05-15 YYYY-MM-дд RESTDateParam
time_field 3:23 вечера ч: мин RESTTimeParam
timestamp_field 1979-10-11T14: 45: 00 YYYY-MM-dd’T’HH: мм: сс RESTTimestampParam
timestamp_with_tzd_field 1979-10-11T14: 45: 00 + 0800 YYYY-MM-dd’T’HH: мм: SSZ RESTTimestampWithTZDParam

Реализация классов пользовательских типов данных

Разбор значения строки даты и преобразование его в java.sql.Date

Во-первых, давайте напишем пользовательский класс типов данных, который обрабатывает параметр date_field , который анализирует строковое представление даты в формате yyyy-MM-dd и превращает его в java.sql.Date .

Коды для RESTDateParam.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.developerscrappad;
 
import java.text.ParseException;
import java.text.SimpleDateFormat;
import javax.ws.rs.WebApplicationException;
 
public class RESTDateParam {
 
    // Declare the date format for the parsing to be correct
    private static final SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd" );
    private java.sql.Date date;
 
    /**
     * This is the default constructor which must take in one string parameter.
     * The parameter is no other than the one passed in through the REST
     * end-point. We'll see it later...
     */
    public RESTDateParam( String dateStr ) throws WebApplicationException {
        try {
            date = new java.sql.Date( df.parse( dateStr ).getTime() );
        } catch ( final ParseException ex ) {
            // Wrap up any expection as javax.ws.rs.WebApplicationException
            throw new WebApplicationException( ex );
        }
    }
 
    /**
     * This is a getter method which returns the parsed date string value as
     * java.sql.Date
     *
     */
    public java.sql.Date getDate() {
        return date;
    }
 
    /**
     * For convenience of result checking
     */
    @Override
    public String toString() {
        if ( date != null ) {
            return date.toString();
        } else {
            return "";
        }
    }
}

Код Объяснение

Здесь мы сначала определяем соответствующий формат даты, например, «гггг-ММ-дд» для SimpleDateFormat для анализа строки даты. После того как конструктор был вызван и после преобразования мы можем получить объект java.sql.Date с помощью метода getDate () . Помимо java.sql.Date, вы можете захотеть, чтобы результирующий объект был либо java.util.Date, либо java.util.Calendar, и это нормально, что во многом зависит от специфики приложения. Здесь, поскольку мы не храним дополнительную информацию о времени и часовом поясе, достаточно простой java.sql.Date.

Как и для остальных классов пользовательских типов данных ниже.

Разбор значения строки времени (с индикатором AM / PM) и преобразование его в java.sql.Time

Коды для RESTTimeParam.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.developerscrappad;
 
import java.text.ParseException;
import java.text.SimpleDateFormat;
import javax.ws.rs.WebApplicationException;
 
public class RESTTimeParam {
 
    private static final SimpleDateFormat df = new SimpleDateFormat( "h:mma" );
    private java.sql.Time time;
 
    public RESTTimeParam( String timeStr ) throws WebApplicationException {
        try {
            time = new java.sql.Time( df.parse( timeStr ).getTime() );
        } catch ( final ParseException ex ) {
            throw new WebApplicationException( ex );
        }
    }
 
    public java.sql.Time getTime() {
        return time;
    }
 
    @Override
    public String toString() {
        if ( time != null ) {
            return time.toString();
        } else {
            return "";
        }
    }
}

Разбор значения строки даты и времени и преобразование его в java.sql.Timestamp

Коды для RESTTimestampParam.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.developerscrappad;
 
import java.text.ParseException;
import java.text.SimpleDateFormat;
import javax.ws.rs.WebApplicationException;
 
public class RESTTimestampParam {
 
    private static final SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss" );
    private java.sql.Timestamp timestamp;
 
    public RESTTimestampParam( String timestampStr ) throws WebApplicationException {
        try {
            timestamp = new java.sql.Timestamp( df.parse( timestampStr ).getTime() );
        } catch ( final ParseException ex ) {
            throw new WebApplicationException( ex );
        }
    }
 
    public java.sql.Timestamp getTimestamp() {
        return timestamp;
    }
 
    @Override
    public String toString() {
        if ( timestamp != null ) {
            return timestamp.toString();
        } else {
            return "";
        }
    }
}

Разбор значения строки времени (с данными часового пояса) и преобразование его в java.util.Date (с информацией о часовом поясе)

Коды для RESTTimestampWithTZDParam.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.developerscrappad;
 
import java.text.ParseException;
import java.text.SimpleDateFormat;
import javax.ws.rs.WebApplicationException;
 
public class RESTTimestampWithTZDParam {
 
    private static final SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ" );
    private java.util.Date date;
 
    public RESTTimestampWithTZDParam( String dateTimeStr ) throws WebApplicationException {
        try {
            date = new java.util.Date( df.parse( dateTimeStr ).getTime() );
        } catch ( final ParseException ex ) {
            throw new WebApplicationException( ex );
        }
    }
 
    public java.util.Date getDate() {
        return date;
    }
 
    @Override
    public String toString() {
        if ( date != null ) {
            return date.toString();
        } else {
            return "";
        }
    }
}

Реализация конечной точки ресурса REST

Таким образом, после того, как необходимые пользовательские типы данных классы для обработки различных форматов даты и времени были определены. Метод конечной точки ресурса REST теперь сможет использовать эти классы для инкапсуляции данных в различных форматах. Все, что нужно сделать, это использовать его непосредственно как тип данных аргументов метода конечной точки. Например:

1
2
3
4
5
6
7
8
9
// ...
@POST
@Path( "/path-root/path-value" )
public Response methodThatHandlesPostRequest(
    @FormParam( "date_field" ) RESTDateParam dateField
) {
    // The rest of the implementation...
}
// ...

Давайте посмотрим на полный пример реализации конечной точки ресурса REST JAX-RS 2.0.

Коды для RESTResource.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.developerscrappad;
 
import javax.json.Json;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
 
@Path( "request-handler" )
public class RESTResource {
 
    @POST
    @Path( "post-request-with-custom-param-data-type" )
    @Produces( "application/json" )
    public Response postRequestWithCustomParamDataType(
        @FormParam( "date_field" ) RESTDateParam dateField, // Put the custom data type to good use
        @FormParam( "time_field" ) RESTTimeParam timeField,
        @FormParam( "timestamp_field" ) RESTTimestampParam timestampField,
        @FormParam( "timestamp_with_tzd_field" ) RESTTimestampWithTZDParam tsWithTZDField
    ) {
        // Output these data as JSON as server response
        String jsonResult = Json.createObjectBuilder()
            .add( "data_submitted", Json.createObjectBuilder()
                .add( "date_field", dateField.toString() )
                .add( "time_field", timeField.toString() )
                .add( "timestamp_field", timestampField.toString() )
                .add( "timestamp_with_tzd_field", tsWithTZDField.toString() )
            ).build().toString();
 
        return getNoCacheResponseBuilder( Response.Status.OK ).entity( jsonResult ).build();
    }
 
    /**
     * Say NO to result caching
     */
    protected ResponseBuilder getNoCacheResponseBuilder( Response.Status status ) {
        CacheControl cc = new CacheControl();
        cc.setNoCache( true );
        cc.setMaxAge( -1 );
        cc.setMustRevalidate( true );
 
        return Response.status( status ).cacheControl( cc );
    }
}

Не забывая инициирующий класс Приложения REST, который расширяет javax.ws.rs.core.Application…

Коды для RESTApplication

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package com.developerscrappad;
 
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
 
@ApplicationPath( "rest-api" )
public class RESTApplication extends Application {
 
    public Set<Class<?>> getClasses() {
        return new HashSet<Class<?>>( Arrays.asList( RESTResource.class ) );
    }
}

Тестирование через клиент HTML с помощью jQuery Ajax POST

Чтобы протестировать классы пользовательских типов данных, была написана простая HTML-страница с использованием jQuery, который выполняет HTTP-запрос POST ajax к URL-адресу конечной точки. Просто упакуйте приведенный ниже HTML-файл как часть веб-приложения, которое будет развернуто для тестирования. Пожалуйста, разверните это на соответствующем сервере приложений или в контейнере сервлетов.

Коды для post-with-custom-param-data-type.html

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html>
    <head>
        <title>Date, Time and Timestamp HTTP Post</title>
    </head>
    <body>
        <div>Date Field: <input id="dateField" type="text" value="1948-05-15" /> (format must be 'yyyy-MM-dd')</div>
        <div>Time Field: <input id="timeField" type="text" value="3:23PM" /> (format must be 'h:mma')</div>
        <div>Timestamp Field: <input id="timestampField" type="text" value="1979-10-11T14:45:00" style="width: 200px;" /> (format must be 'yyyy-MM-ddTHH:mm:ss')</div>
        <div>Timestamp With Time Zone Field: <input id="timestampWithTZDField" type="text" value="1979-10-11T14:45:00+0800" style="width: 200px;" /> (format must be 'yyyy-MM-ddTHH:mm:ss+/-HHmm')</div>
        <div><input type="button" value="Submit" onclick="javascript:performSubmit();" /></div>
        <br /><br />
        <div id="resultJson"></div>
 
        <script type="text/javascript">
            var $ = jQuery.noConflict();
 
            function performSubmit() {
                $.ajax( {
                    url: "rest-api/request-handler/post-request-with-custom-param-data-type",
                    type: "POST",
                    data: {
                        "date_field": $.trim( $( "#dateField" ).val() ),
                        "time_field": $.trim( $( "#timeField" ).val() ),
                        "timestamp_field": $.trim( $( "#timestampField" ).val() ),
                        "timestamp_with_tzd_field": $.trim( $( "#timestampWithTZDField" ).val( ) )
                    },
                    success: function ( resultObj, textStatus, xhr ) {
                        $( "#resultJson" ).html( "<h2>Post Result (JSON)</h2>" + JSON.stringify( resultObj ) );
                    },
                    error: function ( xhr, textStatus, errorThrown ) {
                        $( "#resultJson" ).html( "Something went wrong, status " + xhr.status );
                    }
                } );
            }
        </script>
    </body>
</html>

Результат

После нажатия кнопки « Отправить » клиент HTML должен получить законный ответ JSON от метода конечной точки ресурса REST (путь: post-request-with-custom-param-data-type) и отобразиться в нижней части экран.

Опубликовать результат

Опубликовать результат

Это все. Спасибо за чтение и надеюсь, что это поможет.