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.api.rendering;
020:
021: import java.util.HashMap;
022: import javax.servlet.jsp.JspContext;
023: import javax.servlet.http.HttpServletRequest;
024:
025: import org.apache.beehive.netui.databinding.datagrid.api.sort.SortDirection;
026: import org.apache.beehive.netui.databinding.datagrid.api.DataGridResourceProvider;
027: import org.apache.beehive.netui.databinding.datagrid.api.DataGridConfig;
028: import org.apache.beehive.netui.databinding.datagrid.api.DataGridURLBuilder;
029: import org.apache.beehive.netui.databinding.datagrid.api.DataGridState;
030: import org.apache.beehive.netui.databinding.datagrid.api.DataGridStateFactory;
031: import org.apache.beehive.netui.databinding.datagrid.runtime.rendering.table.TableRenderer;
032: import org.apache.beehive.netui.databinding.datagrid.runtime.util.PagedDataSet;
033: import org.apache.beehive.netui.databinding.datagrid.runtime.util.JspUtil;
034: import org.apache.beehive.netui.tags.rendering.AbstractRenderAppender;
035: import org.apache.beehive.netui.util.Bundle;
036:
037: /**
038: * <p>
039: * The DataGridTagModel is a JavaBean that is exposed by the NetUI data grid tag into
040: * the {@link javax.servlet.jsp.PageContext} in a JSP. This bean exposes state and services
041: * which can be data bound by an expression language in a JSP.
042: * </p>
043: */
044: public class DataGridTagModel {
045:
046: /**
047: * The render state used when the data grid performs its first pass through its tag body.
048: */
049: public static final int RENDER_STATE_START = 10;
050:
051: /**
052: * The render state used when the data grid is rendering the caption tag.
053: */
054: public static final int RENDER_STATE_CAPTION = 20;
055:
056: /**
057: * The render state used when the data grid is rendering the header tag.
058: */
059: public static final int RENDER_STATE_HEADER = 30;
060:
061: /**
062: * The render state used when the data grid is rendering the data rows.
063: */
064: public static final int RENDER_STATE_GRID = 40;
065:
066: /**
067: * The render state used when the data grid is rendering the footer.
068: */
069: public static final int RENDER_STATE_FOOTER = 50;
070:
071: /**
072: * The render state used when the data grid has completed rendering.
073: */
074: public static final int RENDER_STATE_END = 60;
075:
076: private static final int RENDER_STATE_UNINIT = -1;
077:
078: private int _renderState = RENDER_STATE_UNINIT;
079: private boolean _disableDefaultPagerRendering = false;
080: private boolean _renderRowGroups = false;
081: private String _name = null;
082:
083: private JspContext _jspContext = null;
084: private HttpServletRequest _request = null;
085: private StyleModel _styleModel = null;
086: private PagerRenderer _pagerRenderer = null;
087: private PagedDataSet _dataSet = null;
088: private TableRenderer _tableRenderer = null;
089: private DataGridResourceProvider _resourceProvider = null;
090: private DataGridState _dataGridState = null;
091: private DataGridURLBuilder _urlBuilder = null;
092: private DataGridConfig _dataGridConfig = null;
093:
094: /**
095: * Constructor used to build a DataGridTagModel.
096: *
097: * @param name the name of the data grid
098: * @param dataGridConfig the {@link DataGridConfig} object for a data grid
099: * @param jspContext the {@link JspContext} for the JSP in which the data grid's rendering started
100: */
101: public DataGridTagModel(String name, DataGridConfig dataGridConfig,
102: JspContext jspContext) {
103: super ();
104:
105: _jspContext = jspContext;
106: _request = JspUtil.getRequest(_jspContext);
107:
108: _name = name;
109: _dataGridConfig = dataGridConfig;
110:
111: DataGridStateFactory stateFactory = DataGridStateFactory
112: .getInstance(_jspContext);
113: _dataGridState = stateFactory.getDataGridState(_name,
114: _dataGridConfig);
115: _urlBuilder = stateFactory.getDataGridURLBuilder(_name,
116: _dataGridConfig);
117: }
118:
119: /**
120: * Accessor for the name of the data grid.
121: *
122: * @return the name of the data grid
123: */
124: public String getName() {
125: return _name;
126: }
127:
128: /**
129: * Accessor for the {@link JspContext} for the JSP in which the grid started to render.
130: *
131: * @return the {@link JspContext}
132: */
133: public JspContext getJspContext() {
134: return _jspContext;
135: }
136:
137: /**
138: * <p>
139: * Accessor for the current render state. This should be used by clients that need to
140: * affect their behavior based on the data grid's current render state. This value will
141: * be one of:
142: * <ul>
143: * <li>{@link #RENDER_STATE_START}</li>
144: * <li>{@link #RENDER_STATE_CAPTION}</li>
145: * <li>{@link #RENDER_STATE_HEADER}</li>
146: * <li>{@link #RENDER_STATE_GRID}</li>
147: * <li>{@link #RENDER_STATE_FOOTER}</li>
148: * <li>{@link #RENDER_STATE_END}</li>
149: * </ul>
150: * </p>
151: *
152: * @return the current render state
153: */
154: public int getRenderState() {
155: return _renderState;
156: }
157:
158: /**
159: * <p>
160: * Method that alters the data grid's current render to the new <code>renderState</code>. If
161: * the provided render state value is unknown, an {@link IllegalStateException} is thrown. The
162: * data grid cycles through its state in this order:
163: * <ul>
164: * <li>{@link #RENDER_STATE_START}</li>
165: * <li>{@link #RENDER_STATE_CAPTION}</li>
166: * <li>{@link #RENDER_STATE_HEADER}</li>
167: * <li>{@link #RENDER_STATE_GRID}</li>
168: * <li>{@link #RENDER_STATE_FOOTER}</li>
169: * <li>{@link #RENDER_STATE_END}</li>
170: * </ul>
171: * </p>
172: *
173: * @param renderState the DataGridTagModel's new render state
174: * @throws IllegalStateException if an invalid state is provided
175: */
176: public void changeRenderState(int renderState) {
177: switch (_renderState) {
178: case RENDER_STATE_UNINIT:
179: _renderState = RENDER_STATE_START;
180: break;
181: case RENDER_STATE_START:
182: _renderState = RENDER_STATE_CAPTION;
183: break;
184: case RENDER_STATE_CAPTION:
185: _renderState = RENDER_STATE_HEADER;
186: break;
187: case RENDER_STATE_HEADER:
188: _renderState = RENDER_STATE_GRID;
189: break;
190: case RENDER_STATE_GRID:
191: _renderState = RENDER_STATE_FOOTER;
192: break;
193: case RENDER_STATE_FOOTER:
194: _renderState = RENDER_STATE_END;
195: break;
196: default:
197: throw new IllegalStateException(
198: Bundle
199: .getErrorString("DataGridTagModel_InvalidStateTransition"));
200: }
201: }
202:
203: /**
204: * Accessor for the {@link PagedDataSet} that is used to render a data set in the grid.
205: *
206: * @return a {@link PagedDataSet} for the current data set
207: */
208: public PagedDataSet getDataSet() {
209: return _dataSet;
210: }
211:
212: /**
213: * Setter for the {@link PagedDataSet} object. In order to canonicalize the type used by
214: * the data grid to manipulate the data set, the {@link PagedDataSet} is used to
215: * navigate the data set.
216: *
217: * @param dataSet the data set
218: */
219: public void setDataSet(PagedDataSet dataSet) {
220: /* todo: would be nice to address this side-effect outside of the setter */
221: _dataSet = dataSet;
222: _dataGridState.getPagerModel().setDataSetSize(
223: _dataSet.getSize());
224: }
225:
226: /**
227: * Accessor for the {@link PagerRenderer}. This is the {@link PagerRenderer} instance that
228: * will be used to render the UI used to display the pager.
229: *
230: * @return the {@link PagerRenderer} for the data grid
231: */
232: public PagerRenderer getPagerRenderer() {
233: if (_pagerRenderer == null)
234: setPagerRenderer(_dataGridConfig.getDefaultPagerRenderer());
235:
236: return _pagerRenderer;
237: }
238:
239: /**
240: * Set the {@link PagerRenderer} used to render the paging UI for the data grid.
241: *
242: * @param pagerRenderer the {@link PagerRenderer} to use
243: */
244: public void setPagerRenderer(PagerRenderer pagerRenderer) {
245: /* todo: would be nice to address this side-effect outside of the setter */
246: _pagerRenderer = pagerRenderer;
247: _pagerRenderer.setDataGridTagModel(this );
248: }
249:
250: /**
251: * Get the {@link DataGridResourceProvider} used to provide string messages, paths, etc during
252: * data grid rendering.
253: *
254: * @return the {@link DataGridResourceProvider}
255: */
256: public DataGridResourceProvider getResourceProvider() {
257: return _resourceProvider;
258: }
259:
260: /**
261: * Set the {@link DataGridResourceProvider} used to render the data grid.
262: * @param resourceProvider the new resource provider
263: */
264: public void setResourceProvider(
265: DataGridResourceProvider resourceProvider) {
266: _resourceProvider = resourceProvider;
267: }
268:
269: /**
270: * Check to see if the data grid will render its pager UI by default. The location for the default UI
271: * is controlled by the JSP tag doing the rendering.
272: * @return <code>true</code> if default rendering is enabled; <code>false</code> otherwise.
273: */
274: public boolean isDisableDefaultPagerRendering() {
275: return _disableDefaultPagerRendering;
276: }
277:
278: /**
279: * Set a boolean to enable or disable rendering the pager UI by default. If <code>true</code>, the
280: * data grid rendering tags will produce the pager markup in some default location. If <code>false</code>
281: * the default pager rendering will be disabled. The default location is determined by the tags
282: * doing the rendering.
283: * @param disableDefaultPagerRendering boolean for enabling or disabling rendering the pager in the default location
284: */
285: public void setDisableDefaultPagerRendering(
286: boolean disableDefaultPagerRendering) {
287: _disableDefaultPagerRendering = disableDefaultPagerRendering;
288: }
289:
290: /**
291: * <p>
292: * Get the flag for whether to render the data grid using HTML row groups. Row groups include the HTML
293: * <code>thead</code>, <code>tbody</code>, and <code>tfoot</code> tags. If row group rendering is enabled,
294: * the HTML produced by the data grid will be contained inside of these tags and rendered in the correct
295: * order in the produced HTML. More detail on row groups can be found
296: * <a href="http://www.w3.org/TR/REC-html40/struct/tables.html#h-11.2.3">here</a>.
297: * </p>
298: * @return <code>true</code> if row groups will be rendered; <code>false</code> otherwise
299: */
300: public boolean isRenderRowGroups() {
301: return _renderRowGroups;
302: }
303:
304: /**
305: * Set whether to render the data grid using HTML row groups. For more detail, see {@link #isRenderRowGroups()}.
306: *
307: * @param renderRowGroups <code>true</code> if rendering row groups; <code>false</code> otherwise
308: */
309: public void setRenderRowGroups(boolean renderRowGroups) {
310: _renderRowGroups = renderRowGroups;
311: }
312:
313: /**
314: * Get the instance of {@link TableRenderer} that is used to render HTML table markup for a data grid.
315: * @return the {@link TableRenderer}
316: */
317: public TableRenderer getTableRenderer() {
318: return _tableRenderer;
319: }
320:
321: /**
322: * Set the {@link TableRenderer} used to render HTML table markup for a data grid.
323: * @param tableRenderer the {@link TableRenderer} to use for rendering
324: */
325: public void setTableRenderer(TableRenderer tableRenderer) {
326: _tableRenderer = tableRenderer;
327: }
328:
329: /**
330: * Get the {@link StyleModel} used to create style classes during data grid rendering.
331: * @return the {@link StyleModel}
332: */
333: public StyleModel getStyleModel() {
334: return _styleModel;
335: }
336:
337: /**
338: * Set the {@link StyleModel} used to create style classes during data grid rendering.
339: * @param styleModel the {@link StyleModel}
340: */
341: public void setStyleModel(StyleModel styleModel) {
342: _styleModel = styleModel;
343: }
344:
345: /**
346: * Get a message given a resource string name <code>key</code>.
347: * @param key the message key
348: * @return the value of the message
349: */
350: public String getMessage(String key) {
351: assert _resourceProvider != null : "Received a null resource provider";
352: return _resourceProvider.getMessage(key);
353: }
354:
355: /**
356: * Format a message given a resource string name <code>key</code> and a set of
357: * formatting arguments <code>args</code>.
358: * @param key the message key
359: * @param args the arguments used when formatting the message
360: * @return the formatted message
361: */
362: public String formatMessage(String key, Object[] args) {
363: assert _resourceProvider != null : "Received a null resource provider";
364: return _resourceProvider.formatMessage(key, args);
365: }
366:
367: /**
368: * <p>
369: * This method provides support for overriding the messages available in the {@link DataGridResourceProvider} on a
370: * per-message basis. The key and value parameters here will override (or add) a message available via
371: * the {@link DataGridResourceProvider} without requiring an entire Java properties file or custom
372: * {@link DataGridResourceProvider} implementation.
373: * </p>
374: * @param key the key of the message to override
375: * @param value the new value for the message key
376: */
377: public void addResourceOverride(String key, String value) {
378: OverridableDataGridResourceProvider overrideResourceProvider = null;
379: if (!(_resourceProvider instanceof OverridableDataGridResourceProvider)) {
380: overrideResourceProvider = new OverridableDataGridResourceProvider(
381: _resourceProvider);
382: _resourceProvider = overrideResourceProvider;
383: } else {
384: assert _resourceProvider instanceof OverridableDataGridResourceProvider;
385: overrideResourceProvider = (OverridableDataGridResourceProvider) _resourceProvider;
386: }
387:
388: overrideResourceProvider.addResourceOverride(key, value);
389: }
390:
391: /**
392: * <p>
393: * Get the resourrce path used when creating HTML image links during data grid rendering. The value of the
394: * default resource path is the {@link javax.servlet.http.HttpServletRequest#getContextPath()} combined
395: * with the value of the data grid message stringn obtained with the key {@link IDataGridMessageKeys#DATAGRID_RESOURCE_PATH}.
396: * </p>
397: * @return the string resource path
398: */
399: public String getResourcePath() {
400: /* todo: fix the message here to format with the context path */
401: return _request.getContextPath()
402: + "/"
403: + getMessage(IDataGridMessageKeys.DATAGRID_RESOURCE_PATH);
404: }
405:
406: /**
407: * <p>
408: * Get the image paths used for the given {@link SortDirection}. The image paths are discovered
409: * by using the following mapping.
410: * <br/>
411: * <table>
412: * <tr><td>Sort direction</td><td>Message key</td></tr>
413: * <tr><td><code>{@link SortDirection#ASCENDING}</code></td><td><code>{@link IDataGridMessageKeys#SORT_ASC_IMAGE_PATH}</code></td></tr>
414: * <tr><td><code>{@link SortDirection#DESCENDING}</code></td><td><code>{@link IDataGridMessageKeys#SORT_DESC_IMAGE_PATH}</code></td></tr>
415: * <tr><td><code>{@link SortDirection#NONE}</code></td><td><code>{@link IDataGridMessageKeys#SORT_NONE_IMAGE_PATH}</code></td></tr>
416: * </table>
417: * The value for the message is obtained by looking up a value in the {@link DataGridResourceProvider} obtained
418: * via {@link #getResourceProvider()} using the message key in the table above.
419: * </p>
420: * @param sortDirection the {@link SortDirection} used to lookup an image path
421: * @return the string image used to represent a sort direction graphically
422: */
423: public String getSortImagePath(SortDirection sortDirection) {
424: /* todo: move to the DataGridConfig object */
425: if (sortDirection == SortDirection.ASCENDING)
426: return getMessage(IDataGridMessageKeys.SORT_ASC_IMAGE_PATH);
427: else if (sortDirection == SortDirection.DESCENDING)
428: return getMessage(IDataGridMessageKeys.SORT_DESC_IMAGE_PATH);
429: else {
430: assert sortDirection == SortDirection.NONE : "Encountered an invalid sort direction.";
431: return getDefaultSortImagePath();
432: }
433: }
434:
435: /**
436: * Get the default image path used when constructing links to sort images. This value
437: * is taken from the resource String available via the {@link DataGridResourceProvider}
438: * obtained using {@link #getResourceProvider()} using the {@link IDataGridMessageKeys#SORT_NONE_IMAGE_PATH} key.
439: * @return the String path
440: */
441: public String getDefaultSortImagePath() {
442: return getMessage(IDataGridMessageKeys.SORT_NONE_IMAGE_PATH);
443: }
444:
445: /**
446: * Method used to render the data grid's pager UI into the given {@link AbstractRenderAppender}.
447: *
448: * @param appender the {@link AbstractRenderAppender} into which the pager will be rendered
449: */
450: public void renderPager(AbstractRenderAppender appender) {
451: if (getPagerRenderer() != null)
452: appender.append(getPagerRenderer().render());
453: }
454:
455: /**
456: * Accessor for obtaining the {@link DataGridState} object. This is a JavaBean
457: * property that can be accessed via an expression language in order to obtain
458: * access to the state information for the data grid stored in the returned object.
459: *
460: * @return the data grid's {@link DataGridState}
461: */
462: public DataGridState getState() {
463: return _dataGridState;
464: }
465:
466: /**
467: * Accessor for obtaining the {@link DataGridURLBuilder} object. This is a JavaBean
468: * that can be accessed via an expression language in order to obtain access to the
469: * URL information for the data grid stored in the returned object.
470: *
471: * @return the data grid's {@link DataGridURLBuilder}
472: */
473: public DataGridURLBuilder getUrlBuilder() {
474: return _urlBuilder;
475: }
476:
477: /**
478: * Accessor for obtaining the current index in the data set. This value is a zero
479: * based count current item being rendered. For the array {"foo", "bar", "baz"}, the
480: * indices for each item would be 0, 1, and 2. This value does correspond to the
481: * index into an Object array or a list, but in an arbitrary Collection, the index
482: * is simply the number of items that appeared in the Collection before the current one.
483: *
484: * @return the current index
485: */
486: public int getCurrentIndex() {
487: assert _dataSet != null;
488: return _dataSet.getCurrentIndex();
489: }
490:
491: /**
492: * Accessor for obtaining the current item in the data set.
493: *
494: * @return the current item in the data set
495: */
496: public Object getCurrentItem() {
497: assert _dataSet != null;
498: return _dataSet.getCurrentItem();
499: }
500:
501: /**
502: * Accessor for obtaining the data source expression that was used to data bind to the data set.
503: *
504: * @return the String for the data source
505: */
506: public String getDataSource() {
507: assert _dataSet != null;
508: return _dataSet.getDataSource();
509: }
510:
511: /**
512: * Accessor for getting the next item in the data set.
513: *
514: * @return the next item in the data set. Note, depending on the data set, this item could
515: * be <code>null</code>.
516: */
517: public Object nextDataItem() {
518: assert _dataSet != null;
519: return _dataSet.next();
520: }
521:
522: /**
523: * Accessor for determining if there is another item in the data set.
524: *
525: * @return <code>true</code> if there is a next item; <code>false</code> otherwise.
526: */
527: public boolean hasNextDataItem() {
528: assert _dataSet != null;
529: return _dataSet.hasNext();
530: }
531:
532: /* -------------------------------------------------------------
533:
534: Implementation specifics
535:
536: ------------------------------------------------------------- */
537:
538: /**
539: * <p>
540: * An internal class that represents a {@link DataGridResourceProvider} that supports
541: * property-by-property overrides. A JSP is able to specify message keys whose values
542: * to override; in situations where this functionality is used, this class provides
543: * the {@link DataGridResourceProvider} abstraction that implements this override
544: * functionality.
545: * </p>
546: */
547: private final class OverridableDataGridResourceProvider extends
548: DataGridResourceProvider {
549:
550: private DataGridResourceProvider _delegate = null;
551: private HashMap/*<String, String>*/_resourceOverrides = null;
552:
553: private OverridableDataGridResourceProvider(
554: DataGridResourceProvider resourceProvider) {
555: _delegate = resourceProvider;
556: }
557:
558: private void addResourceOverride(String key, String value) {
559: if (_resourceOverrides == null)
560: _resourceOverrides = new HashMap/*<String, String>*/();
561:
562: /* todo: could consider asserting that this key is known by the data grid framework */
563: _resourceOverrides.put(key, value);
564: }
565:
566: public String getMessage(String key) {
567: String msg = internalGetMessage(key);
568: if (msg != null)
569: return msg;
570: else
571: return _delegate.getMessage(key);
572: }
573:
574: public String formatMessage(String key, Object[] args) {
575: String pattern = internalGetMessage(key);
576: if (pattern != null)
577: return internalFormatMessage(pattern, args);
578: else
579: return _delegate.formatMessage(key, args);
580: }
581:
582: private final String internalGetMessage(String key) {
583: if (_resourceOverrides != null
584: && _resourceOverrides.containsKey(key))
585: return (String) _resourceOverrides.get(key);
586: else
587: return null;
588: }
589: }
590: }
|