Статьи

Передача сложных объектов в параметрах URL

Представьте, что вы хотите передать примитивные типы данных, сложные объекты Java, такие как
java.util.Data, java.lang.List, универсальные классы, массивы и все, что вы хотите через параметры URL для того, чтобы предварительно установить значения по умолчанию на любой веб-странице после загрузки страницы. Общая задача? Да, но доступные решения в основном ограничены кодированием / декодированием java.lang.String. Подход, который я покажу, не имеет ограничений на типы данных. Ограничением размера URL является только одно ограничение. URL-адреса длиной более 2083 символов могут работать некорректно в старых версиях IE. Современные Firefox, Opera и Safari могут обрабатывать не менее 80000 символов в URL.

Передача объектов любого типа через параметры URL возможна, если мы сериализуем объекты в JSON и десериализуем их на стороне сервера. Закодированная строка JSON имеет действительный

формат и это путь! Хорошо, но есть проблема. Сериализация / десериализация в / из формата JSON работает нормально, если объект не является универсальным типом. Однако если объект имеет универсальный тип, то информация об универсальном типе теряется из-за стирания Java-типа. Что делать в этом случае? Решение, которое я хочу продемонстрировать, не ограничивается JSF, но, поскольку я разрабатываю внешние интерфейсы Java / Web, я вращаюсь в этом круге… Итак, начнем. Во-первых, нам нужен правильный конвертер для получения параметров URL в формате JSON и их конвертации обратно в Java.
PrimeFaces Extensions предоставляет один — JsonConverter.java. Как это работает? В следующем примере показано, как JsonConverter можно применить к f: viewParam для преобразования списка строк в формате JSON в список в Java.

01
02
03
04
05
06
07
08
09
10
11
<f:metadata>
    <f:viewParam name='subscriptions' value='#{subscriptionController.subscriptions}'>
        <pe:convertJson type='java.util.List<java.lang.String>' />
    </f:viewParam>
</f:metadata>
 
<h:selectManyCheckbox value='#{subscriptionController.subscriptions}'>
    <f:selectItem id='item1' itemLabel='News' itemValue='1' />
    <f:selectItem id='item2' itemLabel='Sports' itemValue='2' />
    <f:selectItem id='item3' itemLabel='Music' itemValue='3' />
</h:selectManyCheckbox>

JsonConverter имеет один необязательный тип атрибута. Нам не нужно предоставлять информацию о типе данных для примитивов, таких как boolean или int. Но, как правило, информация о типе является обязательным атрибутом. Он указывает тип данных объекта значения. Поддерживается любой тип примитива, массив, не универсальный или универсальный тип. Тип состоит из полностью определенных имен классов (кроме примитивов). Примеры:

1
2
3
4
5
6
7
8
'long[]'
'java.lang.String'
'java.util.Date'
'java.util.Collection<java.lang.Integer>'
'java.util.Map<java.lang.String, com.prime.FooPair<java.lang.Integer, java.util.Date>>'
'com.prime.FooNonGenericClass'
'com.prime.FooGenericClass<java.lang.String, java.lang.Integer>'
'com.prime.FooGenericClass<int[], com.prime.FooGenericClass<com.prime.FooNonGenericClass, java.lang.Boolean>>'

Строка в типе анализируется во время выполнения. Код для JsonConverter доступен здесь (для читателей, которые интересуются подробностями). JsonConverter основан на трех других классах: ParameterizedTypeImpl.java ,
GsonConverter.java и DateTypeAdapter.java . Последний является специальным адаптером для дат, потому что java.util.Date следует конвертировать в миллисекунды и обратно в java.util.Date. Все идет нормально. Но как подготовить значения в качестве параметров URL на стороне Java? Я покажу служебный класс, который можно использовать для этого. Прочитайте комментарии, пожалуйста, они говорят сами за себя.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import org.apache.log4j.Logger;
import org.primefaces.extensions.converter.JsonConverter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
 
/**
 * Builder for request parameters.
 */
public class RequestParameterBuilder {
 
    private Logger LOG = Logger.getLogger(RequestParameterBuilder.class);
 
    private StringBuilder buffer;
    private String originalUrl;
    private JsonConverter jsonConverter;
    private String encoding;
    private boolean added;
 
    /**
     * Creates a builder instance by the current request URL.
     */
    public RequestParameterBuilder() {
        this(((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).getRequestURL()
            .toString());
    }
 
    /**
     * Creates a builder instance by the given URL.
     *
     * @param url URL
     */
    public RequestParameterBuilder(String url) {
        buffer = new StringBuilder(url);
        originalUrl = url;
        jsonConverter = new JsonConverter();
 
        encoding = FacesContext.getCurrentInstance().getExternalContext().getRequestCharacterEncoding();
        if (encoding == null) {
            encoding = 'UTF-8';
        }
    }
 
    /**
     * Adds a request parameter to the URL without specifying a data type of the given parameter value.
     * Parameter's value is converted to JSON notation when adding. Furthermore, it will be encoded
     * according to the acquired encoding.
     *
     * @param name name of the request parameter
     * @param value value of the request parameter
     * @return RequestParameterBuilder updated this instance which can be reused
     */
    public RequestParameterBuilder paramJson(String name, Object value) throws UnsupportedEncodingException {
        return paramJson(name, value, null);
    }
 
    /**
     * Adds a request parameter to the URL with specifying a data type of the given parameter value. Data type is sometimes
     * required, especially for Java generic types, because type information is erased at runtime and the conversion to JSON
     * will not work properly. Parameter's value is converted to JSON notation when adding. Furthermore, it will be encoded
     * according to the acquired encoding.
     *
     * @param name name of the request parameter
     * @param value value of the request parameter
     * @param type data type of the value object. Any primitive type, array, non generic or generic type is supported.
     *             Data type is sometimes required to convert a value to a JSON representation. All data types should be
     *             fully qualified.
     * @return RequestParameterBuilder updated this instance which can be reused
     */
    public RequestParameterBuilder paramJson(String name, Object value, String type)
        throws UnsupportedEncodingException {
        jsonConverter.setType(type);
 
        String jsonValue;
        if (value == null) {
            jsonValue = 'null';
        } else {
            jsonValue = jsonConverter.getAsString(null, null, value);
        }
 
        if (added || originalUrl.contains('?')) {
            buffer.append('&');
        } else {
            buffer.append('?');
        }
 
        buffer.append(name);
        buffer.append('=');
        buffer.append(URLEncoder.encode(jsonValue, encoding));
 
        // set a flag that at least one request parameter was added
        added = true;
 
        return this;
    }
 
    /**
     * Adds a request parameter to the URL. This is a convenient method for primitive, plain data types.
     * Parameter's value will not be converted to JSON notation when adding. It will be only encoded
     * according to the acquired encoding. Note: null values will not be added.
     *
     * @param name name of the request parameter
     * @param value value of the request parameter
     * @return RequestParameterBuilder updated this instance which can be reused
     */
    public RequestParameterBuilder param(String name, Object value) throws UnsupportedEncodingException {
        if (value == null) {
            return this;
        }
 
        if (added || originalUrl.contains('?')) {
            buffer.append('&');
        } else {
            buffer.append('?');
        }
 
        buffer.append(name);
        buffer.append('=');
        buffer.append(URLEncoder.encode(value.toString(), encoding));
 
        // set a flag that at least one request parameter was added
        added = true;
 
        return this;
    }
 
    /**
     * Builds the end result.
     *
     * @return String end result
     */
    public String build() {
        String url = buffer.toString();
 
        if (url.length() > 2083) {
            LOG.error('URL ' + url + ' is longer than 2083 chars (' + buffer.length() +
                '). It may not work properly in old IE versions.');
        }
 
        return url;
    }
 
    /**
     * Resets the internal state in order to be reused.
     *
     * @return RequestParameterBuilder reseted builder
     */
    public RequestParameterBuilder reset() {
        buffer = new StringBuilder(originalUrl);
        jsonConverter.setType(null);
        added = false;
 
        return this;
    }
}

Обычно бин, использующий RequestParameterBuilder, предоставляет параметризованный URL, вызывая paramJson (…) или param (…).

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
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
 
/**
 * UrlParameterProvider bean.
 */
@ManagedBean
@SessionScoped
public class UrlParameterProvider implements Serializable {
 
    private String parametrizedUrl;
 
    @PostConstruct
    protected void initialize() {
        RequestParameterBuilder rpBuilder = new RequestParameterBuilder('/views/examples/params.jsf');
        try {
            List<String> subscriptions = new ArrayList<String>();
            tableBlockEntries.add('2');
            tableBlockEntries.add('3');
 
            // add the list to URL parameters with conversion to JSON
            rpBuilder.paramJson('subscriptions', subscriptions, 'java.util.List<java.lang.String>');
 
            // add int values to URL parameters without conversion to JSON (just for example)
            rpBuilder.param('min', 20);
            rpBuilder.param('max', 80);  
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
 
        parametrizedUrl = rpBuilder.build();
    }
 
    public String getParametrizedUrl() {
        return parametrizedUrl;
    }
}

Использование в XHTML — пример с h: outputLink

1
2
3
<h:outputLink value='#{urlParameterProvider.parametrizedUrl}'>
    Parametrized URL
</h:outputLink>

Когда пользователь нажимает на ссылку и попадает на целевую страницу с относительным путем / views / examples / params.jsf, он / она увидит предварительно проверенный h: selectManyCheckbox. Реальный мир сложнее. На самом деле я написал много пользовательских конвертеров с JsonConverter внутри. Так что вместо <pe: convertJson type = ‘…’ /> добавлены пользовательские конвертеры. Эта тема выходит за рамки этого поста.

Ссылка: Передача сложных объектов в параметрах URL от нашего партнера по JCG Олега Вараксина в блоге Мысли о разработке программного обеспечения .