001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.databinding.datagrid.runtime.config;
020:
021: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
022:
023: import java.util.HashMap;
024: import java.util.Map;
025: import java.util.List;
026: import java.util.Iterator;
027: import java.util.ArrayList;
028:
029: import org.apache.beehive.netui.databinding.datagrid.api.sort.Sort;
030: import org.apache.beehive.netui.databinding.datagrid.api.sort.SortModel;
031: import org.apache.beehive.netui.databinding.datagrid.api.sort.SortDirection;
032: import org.apache.beehive.netui.databinding.datagrid.api.DataGridState;
033: import org.apache.beehive.netui.databinding.datagrid.api.pager.PagerModel;
034: import org.apache.beehive.netui.databinding.datagrid.api.filter.FilterModel;
035: import org.apache.beehive.netui.databinding.datagrid.api.filter.FilterOperation;
036: import org.apache.beehive.netui.databinding.datagrid.api.filter.Filter;
037: import org.apache.beehive.netui.databinding.datagrid.api.DataGridStateCodec;
038: import org.apache.beehive.netui.databinding.datagrid.api.DataGridConfig;
039: import org.apache.beehive.netui.databinding.datagrid.api.DataGridURLBuilder;
040: import org.apache.beehive.netui.databinding.datagrid.runtime.sql.SQLSupport;
041: import org.apache.beehive.netui.util.logging.Logger;
042: import org.apache.beehive.netui.util.Bundle;
043: import org.apache.beehive.netui.pageflow.internal.InternalConstants;
044:
045: /**
046: * <p>
047: * Derault implementation of the {@link DataGridStateCodec} abstract base class. This class provides support
048: * for obtaining a {@link DataGridState} object which contains "current" state for a data grid and will
049: * be used when rendering a data grid.
050: * </p>
051: */
052: class DefaultDataGridStateCodec extends DataGridStateCodec {
053:
054: /* filter format: netui_filter=<namespace>;<fExpr>~<fOp>~<fVal>,<fExpr>~<fOp>~<fVal> */
055: /* sort format: netui_sort=<namespace>;<expr>,-<expr> */
056: /* row format: netui_row=<namespace>~<row> */
057: /* page size format: netui_pagesize=<namespace>~<pagesize> */
058:
059: static final String PARAM_KEY_FILTER = "netui_filter";
060: static final String PARAM_KEY_SORT = "netui_sort";
061: static final String PARAM_KEY_PAGE_SIZE = "netui_pagesize";
062: static final String PARAM_KEY_ROW = "netui_row";
063:
064: private static final Logger LOGGER = Logger
065: .getInstance(DefaultDataGridStateCodec.class);
066: private static final String DELIM_GRID_NAME = ";";
067: private static final String DELIM_SORT_TERM = ",";
068: private static final String DELIM_FILTER_TERM = ",";
069: private static final String DELIM_FILTER = "~";
070: private static final String SORT_DESCENDING = "-";
071:
072: private static final int DEFAULT_PAGE_SIZE = 10;
073: private static final int DEFAULT_ROW = 0;
074:
075: private boolean _decoded = false;
076:
077: /**
078: * The ServletRequest needs to be processed such that the parameter values of
079: * interest are removed from the query param map. Then, the Map is stateless
080: * relative to the state that the current data grid needs to add.
081: */
082: private HashMap _queryParams = null;
083: private DataGridConfig _config = null;
084: private DataGridState _state = null;
085: private DefaultDataGridURLBuilder _urlBuilder = null;
086:
087: /**
088: * Package protected constructor; this class should only be constructed via the {@link DefaultDataGridConfig}
089: * class.
090: * @param config the data grid config object used to manufacture a state object
091: */
092: DefaultDataGridStateCodec(DataGridConfig config) {
093: super ();
094: _config = config;
095: _state = _config.createDataGridState();
096: _urlBuilder = new DefaultDataGridURLBuilder(this );
097: }
098:
099: /**
100: * Get the current {@link DataGridState}.
101: * @return the data grid state
102: */
103: public DataGridState getDataGridState() {
104: if (!_decoded) {
105: decode(getServletRequest().getParameterMap());
106: }
107: return _state;
108: }
109:
110: /**
111: * Get the {@link DataGridURLBuilder} for this state codec. The URL builder can be used to build
112: * URLs managing the {@link DataGridState} obtainable via {@link #getDataGridState()}.
113: * @return the data grid URL builder
114: */
115: public DataGridURLBuilder getDataGridURLBuilder() {
116: return _urlBuilder;
117: }
118:
119: /**
120: * <p>
121: * Set the {@link DataGridState} object. This mechanism provides callers a way to explicitly set the
122: * {@link DataGridState}. This useful when a grid's state needs to be provided from an outside source
123: * and attached so it is obtainable from the data grid via the usual mechanism.
124: * </p>
125: *
126: * @param state the new data grid state
127: */
128: public void setDataGridState(DataGridState state) {
129: _state = state;
130: }
131:
132: /**
133: * Returns the existing query parameters map. This is a clone that can be augmented by client code but the
134: * existing parameters are not changed.
135: *
136: * @return
137: */
138: Map getExistingParams() {
139: return _queryParams;
140: }
141:
142: /**
143: * Build the sort parameter map given this list of {@link Sort} instances. Note, the query parameters returned
144: * here are <b>not</b> URL encoded. The map contains key / value pairs as (String, String[]).
145: * @param sorts the sorts
146: * @return a map containing the sort query parameters
147: */
148: Map buildSortParamMap(List sorts) {
149: if (sorts == null || sorts.size() == 0)
150: return null;
151:
152: String encoded = encodeSorts(sorts);
153: if (encoded == null)
154: return null;
155: else {
156: HashMap params = new HashMap();
157: params.put(PARAM_KEY_SORT, new String[] { encoded });
158: return params;
159: }
160: }
161:
162: /**
163: * Build the filter parameter map given this list of {@link Filter} instances. Note, the query parameters
164: * returned here are <b>not</b> URL encoded. The map contains key / value pairs as (String, String[]).
165: * @param filters the filters
166: * @return a map containing the filter query parameters
167: */
168: Map buildFilterParamMap(List filters) {
169: if (filters == null || filters.size() == 0)
170: return null;
171:
172: String encoded = encodeFilters(filters);
173: if (encoded == null)
174: return null;
175: else {
176: HashMap params = new HashMap();
177: params.put(PARAM_KEY_FILTER, new String[] { encoded });
178: return params;
179: }
180: }
181:
182: /**
183: * Build the URL parameter map given a current row and page size. Note, the query parameters returned
184: * here are <b>not</b> URL encoded. The map contains key / value pairs as (String, String[]).
185: * @param row the current row
186: * @param pageSize the current page size
187: * @return a map containing the pager query parameters
188: */
189: Map buildPageParamMap(Integer row, Integer pageSize) {
190: HashMap map = new HashMap();
191: if (row != null && row.intValue() != DEFAULT_ROW)
192: map.put(PARAM_KEY_ROW, new String[] { encodeRow(row
193: .intValue()) });
194:
195: /* only encode the page size if it is not equal to the default page size for this data grid
196:
197: for example, if a data grid's default page size is 20 but is set somehow by the application
198: to be 50, the default will read 20 but overridden pageSize value should be encoded in the URL
199: */
200: if (pageSize != null
201: && pageSize.intValue() != _state.getPagerModel()
202: .getDefaultPageSize())
203: map
204: .put(PARAM_KEY_PAGE_SIZE,
205: new String[] { encodePageSize(pageSize
206: .intValue()) });
207:
208: return map;
209: }
210:
211: /**
212: * Decode a Map of URL parameters. This method will convert a complete set of URL parameters into several
213: * buckets including the sorts, filters, and paging information for the data grid name associated with this
214: * state codec. In addition, a bucket of 'other' parameters is also collected which are the ones that
215: * were in the current request URL and should be maintained on all generated URLs.
216: * @param parameters the list of parameters to decode
217: */
218: private void decode(Map parameters) {
219: _decoded = true;
220:
221: String namespacePrefix = getGridName() + ";";
222:
223: Iterator keys = parameters.keySet().iterator();
224: while (keys.hasNext()) {
225: String key = (String) keys.next();
226: String[] values = (String[]) parameters.get(key);
227:
228: if (key.equals(PARAM_KEY_SORT)) {
229: List sorts = null;
230: for (int i = 0; i < values.length; i++) {
231: String value = values[i];
232: if (value.startsWith(namespacePrefix))
233: sorts = decodeSort(value);
234: else
235: addParam(key, value);
236: }
237: SortModel sortModel = _config.createSortModel(sorts);
238: _state.setSortModel(sortModel);
239: } else if (key.equals(PARAM_KEY_FILTER)) {
240: List filters = null;
241: for (int i = 0; i < values.length; i++) {
242: String value = values[i];
243: if (value.startsWith(namespacePrefix))
244: filters = decodeFilter(value);
245: else
246: addParam(key, value);
247: }
248: FilterModel filterModel = _config
249: .createFilterModel(filters);
250: _state.setFilterModel(filterModel);
251: } else if (key.equals(PARAM_KEY_ROW)) {
252: int row = DEFAULT_ROW;
253: for (int i = 0; i < values.length; i++) {
254: String value = values[i];
255: if (value.startsWith(namespacePrefix))
256: row = decodeRow(value).intValue();
257: else
258: addParam(key, value);
259: }
260: PagerModel pagerModel = _state.getPagerModel();
261: if (pagerModel == null) {
262: pagerModel = _config.createPagerModel();
263: _state.setPagerModel(pagerModel);
264: }
265: pagerModel.setRow(row);
266: } else if (key.equals(PARAM_KEY_PAGE_SIZE)) {
267: int pageSize = DEFAULT_PAGE_SIZE;
268: for (int i = 0; i < values.length; i++) {
269: String value = values[i];
270: if (value.startsWith(namespacePrefix))
271: pageSize = decodeRow(value).intValue();
272: else
273: addParam(key, value);
274: }
275: PagerModel pagerModel = _state.getPagerModel();
276: if (pagerModel == null) {
277: pagerModel = _config.createPagerModel();
278: _state.setPagerModel(pagerModel);
279: }
280: pagerModel.setPageSize(pageSize);
281: } else if (key
282: .startsWith(InternalConstants.ACTION_OVERRIDE_PREFIX)) {
283: // discard the param
284: } else
285: addParam(key, values);
286: }
287:
288: /* ensure that there is something created for the grid state model objects */
289: if (_state.getSortModel() == null)
290: _state.setSortModel(_config.createSortModel(null));
291: if (_state.getFilterModel() == null)
292: _state.setFilterModel(_config.createFilterModel(null));
293: if (_state.getPagerModel() == null)
294: _state.setPagerModel(_config.createPagerModel());
295: }
296:
297: private void addParam(String key, String value) {
298: if (_queryParams == null)
299: _queryParams = new HashMap();
300:
301: ArrayList list = (ArrayList) _queryParams.get(key);
302: if (list == null) {
303: list = new ArrayList();
304: _queryParams.put(key, list);
305: }
306:
307: list.add(value);
308: }
309:
310: private void addParam(String key, String[] values) {
311: if (_queryParams == null)
312: _queryParams = new HashMap();
313:
314: ArrayList list = (ArrayList) _queryParams.get(key);
315: if (list == null) {
316: list = new ArrayList();
317: _queryParams.put(key, list);
318: }
319:
320: for (int i = 0; i < values.length; i++) {
321: list.add(values[i]);
322: }
323: }
324:
325: private int decodeInt(String value, int defaultValue) {
326: int intValue = defaultValue;
327: try {
328: intValue = Integer.parseInt(value);
329: } catch (NumberFormatException nfe) {
330: LOGGER.error(Bundle.getErrorString(
331: "DataGridStateCodec_IllegalIntegerValue",
332: new Object[] { value, nfe }));
333: }
334: return intValue;
335: }
336:
337: /*
338: Sort handling
339: */
340: private List decodeSort(String value) {
341: ArrayList sorts = new ArrayList();
342:
343: String[] nameAndSorts = value.split(DELIM_GRID_NAME);
344: if (nameAndSorts.length != 2)
345: return null;
346:
347: String namespace = nameAndSorts[0];
348: String[] sortStrings = nameAndSorts[1].split(DELIM_SORT_TERM);
349:
350: // find the list of sorted columns
351: // two columns of the bugs grid would be sorted as:
352: //
353: // netui_sort=bugs~id,-priority
354: for (int i = 0; i < sortStrings.length; i++) {
355: String sort = sortStrings[i];
356: SortDirection sortDirection = SortDirection.NONE;
357: if (sort.startsWith("-"))
358: sortDirection = SortDirection.DESCENDING;
359: else
360: sortDirection = SortDirection.ASCENDING;
361: String sortExpression = (sortDirection == SortDirection.DESCENDING ? sort
362: .substring(1)
363: : sort);
364: Sort gridSort = _config.createSort();
365: gridSort.setSortExpression(sortExpression);
366: gridSort.setDirection(sortDirection);
367: sorts.add(gridSort);
368: }
369:
370: return sorts;
371: }
372:
373: String encodeSorts(List sorts) {
374: boolean hasSorts = false;
375: InternalStringBuilder sb = new InternalStringBuilder(16);
376: sb.append(getGridName());
377: sb.append(DELIM_GRID_NAME);
378: for (int i = 0; i < sorts.size(); i++) {
379: Sort sort = (Sort) sorts.get(i);
380:
381: if (sort.getDirection() == SortDirection.NONE)
382: continue;
383:
384: if (hasSorts)
385: sb.append(DELIM_SORT_TERM);
386: else
387: hasSorts = true;
388:
389: if (sort.getDirection() == SortDirection.DESCENDING)
390: sb.append(SORT_DESCENDING);
391: sb.append(sort.getSortExpression());
392: }
393:
394: if (!hasSorts)
395: return null;
396: else
397: return sb.toString();
398: }
399:
400: /*
401: Filter handling
402: */
403: private List decodeFilter(String value) {
404: String[] nameAndFilters = value.split(DELIM_GRID_NAME);
405:
406: assert nameAndFilters.length == 2;
407:
408: String namespace = nameAndFilters[0];
409: String[] filters = nameAndFilters[1].split(DELIM_FILTER_TERM);
410:
411: ArrayList/*<Filter>*/gridFilters = new ArrayList/*<Filter>*/();
412: for (int i = 0; i < filters.length; i++) {
413: String[] terms = filters[i].split(DELIM_FILTER);
414: Filter filter = null;
415:
416: if (terms.length == 2 && terms[1].equals("*"))
417: continue;
418: else if (terms.length == 3) {
419: FilterOperation fOp = SQLSupport
420: .mapFilterAbbreviationToOperation(terms[1]);
421: filter = _config.createFilter();
422: filter.setFilterExpression(terms[0]);
423: filter.setOperation(fOp);
424: filter.setValue(terms[2]);
425: } else {
426: LOGGER.error(Bundle.getErrorString(
427: "DataGridStateCodec_IllegalFilter",
428: new Object[] { filter }));
429: continue;
430: }
431:
432: assert filter != null;
433: gridFilters.add(filter);
434: }
435: return gridFilters;
436: }
437:
438: String encodeFilters(List filters) {
439: boolean hasFilters = false;
440: InternalStringBuilder sb = new InternalStringBuilder();
441: sb.append(getGridName());
442: sb.append(DELIM_GRID_NAME);
443: for (int i = 0; i < filters.size(); i++) {
444: Filter filter = (Filter) filters.get(i);
445:
446: if (hasFilters)
447: sb.append(DELIM_FILTER_TERM);
448:
449: sb.append(filter.getFilterExpression());
450: sb.append(DELIM_FILTER);
451: sb.append(filter.getOperation().getAbbreviation());
452: sb.append(DELIM_FILTER);
453: sb.append(filter.getValue());
454:
455: hasFilters = true;
456: }
457:
458: return sb.toString();
459: }
460:
461: /*
462: Pager handling
463: */
464: private Integer decodeRow(final String page) {
465: String[] terms = page.split(DELIM_GRID_NAME);
466:
467: /* todo: this is really an exception, not an assert */
468: assert terms != null && terms.length == 2;
469: String intString = terms[1];
470: return new Integer(decodeInt(intString, DEFAULT_ROW));
471: }
472:
473: String encodeRow(final int row) {
474: InternalStringBuilder sb = new InternalStringBuilder(16);
475: sb.append(getGridName());
476: sb.append(DELIM_GRID_NAME);
477: sb.append(row);
478: return sb.toString();
479: }
480:
481: private Integer decodePageSize(final String pageSize) {
482: String[] terms = pageSize.split(DELIM_GRID_NAME);
483:
484: /* todo: this is really an exception, not an assert */
485: assert terms != null && terms.length == 2;
486: String intString = terms[1];
487: return new Integer(decodeInt(intString, DEFAULT_PAGE_SIZE));
488: }
489:
490: String encodePageSize(final int pageSize) {
491: InternalStringBuilder sb = new InternalStringBuilder(16);
492: sb.append(getGridName());
493: sb.append(DELIM_GRID_NAME);
494: sb.append(pageSize);
495: return sb.toString();
496: }
497: }
|