001: /*
002: * Copyright 2006-2007 Luca Garulli (luca.garulli@assetdata.it)
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.romaframework.module.crud;
018:
019: import java.lang.reflect.InvocationTargetException;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.List;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.romaframework.aspect.core.annotation.AnnotationConstants;
027: import org.romaframework.aspect.core.annotation.CoreClass;
028: import org.romaframework.aspect.core.annotation.CoreField;
029: import org.romaframework.aspect.persistence.PersistenceAspect;
030: import org.romaframework.aspect.persistence.PersistenceConstants;
031: import org.romaframework.aspect.persistence.QueryByExample;
032: import org.romaframework.aspect.persistence.QueryByFilter;
033: import org.romaframework.aspect.persistence.QueryByText;
034: import org.romaframework.aspect.persistence.annotation.Persistence;
035: import org.romaframework.aspect.reporting.ReportingAspect;
036: import org.romaframework.aspect.reporting.annotation.ReportingField;
037: import org.romaframework.aspect.view.ViewAspect;
038: import org.romaframework.aspect.view.ViewCallback;
039: import org.romaframework.aspect.view.ViewConstants;
040: import org.romaframework.aspect.view.annotation.ViewAction;
041: import org.romaframework.aspect.view.annotation.ViewField;
042: import org.romaframework.aspect.view.feature.ViewActionFeatures;
043: import org.romaframework.aspect.view.feature.ViewClassFeatures;
044: import org.romaframework.aspect.view.feature.ViewElementFeatures;
045: import org.romaframework.aspect.view.form.SelectableInstance;
046: import org.romaframework.aspect.view.page.CallerHandler;
047: import org.romaframework.core.classloader.ClassLoaderListener;
048: import org.romaframework.core.config.Refreshable;
049: import org.romaframework.core.domain.message.Message;
050: import org.romaframework.core.domain.message.MessageOk;
051: import org.romaframework.core.domain.message.MessageResponseListener;
052: import org.romaframework.core.domain.message.MessageText;
053: import org.romaframework.core.domain.message.MessageYesNo;
054: import org.romaframework.core.domain.reporting.ReportGenerator;
055: import org.romaframework.core.entity.ComposedEntity;
056: import org.romaframework.core.entity.EntityHelper;
057: import org.romaframework.core.flow.Controller;
058: import org.romaframework.core.flow.ObjectContext;
059: import org.romaframework.core.schema.SchemaClass;
060: import org.romaframework.core.schema.SchemaManager;
061:
062: /**
063: * Main class to display CRUD entry point. It allows to make queries using the filter on top of the page. Results are displayed on
064: * bottom of the page along with management of multi-pages. You can change CRUD behavior and layout in the subclass or by Xml
065: * Annotation.
066: *
067: * @author Luca Garulli (luca.garulli@assetdata.it)
068: * @param <T>
069: * ComposedEntity class used to display the result in the table.
070: */
071: @CoreClass(orderFields="filter paging result",orderActions="search create read update delete selectAll deselectAll")
072: public abstract class CRUDMain<T extends ComposedEntity<?>> extends
073: SelectableInstance implements CallerHandler, PagingListener,
074: MessageResponseListener, Refreshable, ClassLoaderListener,
075: ViewCallback {
076:
077: @ViewField(visible=AnnotationConstants.FALSE)
078: protected int pageElements;
079:
080: @ReportingField(visible=AnnotationConstants.FALSE)
081: @ViewField(label="",render=ViewConstants.RENDER_OBJECTEMBEDDED,layout="form://paging")
082: protected CRUDPaging paging;
083:
084: protected org.romaframework.aspect.persistence.Query queryRequest;
085:
086: protected Class<?> listClass;
087:
088: protected Class<?> createClass;
089:
090: protected Class<?> readClass;
091:
092: protected Class<?> updateClass;
093:
094: @ViewField(visible=AnnotationConstants.FALSE)
095: protected Object backObject;
096:
097: protected static Log log = LogFactory.getLog(CRUDMain.class);
098:
099: public static final int DEF_PAGE_ELEMENTS = 15;
100:
101: protected static final String QUERY_ALL_MESSAGE = "queryAll";
102:
103: public CRUDMain(Class<? extends ComposedEntity<?>> iListClass,
104: Class<?> iCreateClass, Class<?> iReadClass,
105: Class<?> iEditClass) {
106: Controller.getInstance().registerListener(
107: ClassLoaderListener.class, this );
108:
109: pageElements = DEF_PAGE_ELEMENTS;
110: paging = new CRUDPaging(this );
111:
112: listClass = iListClass;
113: createClass = iCreateClass;
114: readClass = iReadClass;
115: updateClass = iEditClass;
116: }
117:
118: public void onShow() {
119: if (!ObjectContext.getInstance().existComponent(
120: ReportingAspect.class.getSimpleName()))
121: ObjectContext.getInstance().setActionFeature(this ,
122: ViewAspect.ASPECT_NAME, "report",
123: ViewActionFeatures.VISIBLE, Boolean.FALSE);
124: }
125:
126: @CoreField(embedded=AnnotationConstants.TRUE)
127: @ViewField(label="",layout="form://filter")
128: public abstract ComposedEntity<?> getFilter();
129:
130: @ViewField(label="",selectionField="selection",render=ViewConstants.RENDER_TABLE,enabled=AnnotationConstants.FALSE,layout="form://result")
131: public abstract List<? extends Object> getResult();
132:
133: public abstract void setResult(Object iValue);
134:
135: public void onDispose() {
136: Controller.getInstance().unregisterListener(
137: ClassLoaderListener.class, this );
138: }
139:
140: @ViewAction(visible=AnnotationConstants.FALSE)
141: public void refresh() {
142: search();
143: }
144:
145: public void loadPage(int iPage) {
146: if (queryRequest == null)
147: return;
148:
149: if (iPage == 0) {
150: // QUERY ALL
151: MessageYesNo confirm = new MessageYesNo(QUERY_ALL_MESSAGE,
152: "Query All confirmation", this );
153: confirm.setMessage("$CRUDMain.queryAll.confirm");
154: ObjectContext.getInstance().show(confirm);
155: } else {
156: int rangeFrom = (paging.getCurrentPage() - 1)
157: * pageElements;
158: queryRequest.setRangeFrom(rangeFrom, rangeFrom
159: + pageElements);
160: executeQuery();
161: }
162: }
163:
164: public CRUDPaging getPaging() {
165: return paging;
166: }
167:
168: /**
169: * Generate a report for the current view using the ReportingAspect.
170: */
171: public void report() {
172: ReportGenerator form = new ReportGenerator(this , this
173: .getClass().getSimpleName());
174: ObjectContext.getInstance().show(form);
175: }
176:
177: /**
178: * By default search uses the "Search By Example" pattern by invoking the searchByExample() method. Overwrite this to use the
179: * searchByQuery() or any other mode.
180: */
181: @Persistence(mode=PersistenceConstants.MODE_TX)
182: public void search() {
183: searchByExample();
184: }
185:
186: /**
187: * Search entities using the "Search By Example" pattern. All filter's properties with value different by null are evaluated in
188: * the query predicate.
189: */
190: @ViewAction(visible=AnnotationConstants.FALSE)
191: public void searchByExample() {
192: Object filter = ((ComposedEntity<?>) getFilter()).getEntity();
193: queryRequest = new QueryByExample(filter.getClass(), filter,
194: null);
195: queryRequest.setStrategy(PersistenceAspect.STRATEGY_DETACHING);
196:
197: executePagingQuery();
198: }
199:
200: /**
201: * Search entities using the "Search By Example" pattern. All filter's properties with value different by null are evaluated in
202: * the query predicate. iAdditionalFilter allow to specify additional constaints for the autogenerated QBE.
203: */
204: @ViewAction(visible=AnnotationConstants.FALSE)
205: public void searchByExample(QueryByFilter iAdditionalFilter) {
206: Object filter = ((ComposedEntity<?>) getFilter()).getEntity();
207: queryRequest = new QueryByExample(filter.getClass(), filter,
208: iAdditionalFilter);
209: queryRequest.setStrategy(PersistenceAspect.STRATEGY_DETACHING);
210:
211: executePagingQuery();
212: }
213:
214: /**
215: * Search entities using the "Search By Example" pattern. This variant uses a QueryFilter instance as filter. This allow a much
216: * more powerful control over filtering by simple QBE upon.
217: */
218: @ViewAction(visible=AnnotationConstants.FALSE)
219: public void searchByFilter(QueryByFilter iQueryFilter) {
220: queryRequest = iQueryFilter;
221: queryRequest.setStrategy(PersistenceAspect.STRATEGY_DETACHING);
222: executePagingQuery();
223: }
224:
225: /**
226: * Search entities by passing a query text. The language of query text must be understood by underline Persistence Aspect.
227: *
228: * @param iText
229: * Query text in the language supported by underline Persistence Aspect
230: */
231: @ViewAction(visible=AnnotationConstants.FALSE)
232: public void searchByQuery(String iText) {
233: Object filter = ((ComposedEntity<?>) getFilter()).getEntity();
234: queryRequest = new QueryByText(filter.getClass(), iText);
235: queryRequest.setStrategy(PersistenceAspect.STRATEGY_DETACHING);
236:
237: executePagingQuery();
238: }
239:
240: protected void executePagingQuery() {
241: queryRequest.setRangeFrom(0, pageElements);
242: executeQuery();
243:
244: paging.setTotalItems(queryRequest.getTotalItems());
245: ObjectContext.getInstance().refresh(paging, "totalItems");
246:
247: if (paging != null) {
248: int pagesOffset = queryRequest.getTotalItems()
249: % pageElements;
250: paging.setPages(queryRequest.getTotalItems() / pageElements
251: + (pagesOffset != 0 ? 1 : 0));
252: paging.setCurrentPage(1);
253: }
254:
255: if (getResult().size() == 1)
256: setSelection(new Object[] { getResult().get(0) });
257: else
258: setSelection(null);
259: }
260:
261: protected void executeQuery() {
262: List<Object> repositoryResult = ObjectContext.getInstance()
263: .getContextComponent(PersistenceAspect.class).query(
264: queryRequest);
265: fillResult(repositoryResult);
266: }
267:
268: /*
269: * Fill CRUD result with the query result
270: */
271: private void fillResult(List<Object> repositoryResult) {
272: List<Object> tempResult = repositoryResult;
273: if (ComposedEntity.class.isAssignableFrom(listClass)) {
274: // CREATE THE COMPOSED RESULT SET
275: tempResult = new ArrayList<Object>();
276: Object entity;
277: if (repositoryResult != null)
278: for (int i = 0; i < repositoryResult.size(); ++i) {
279: try {
280: entity = EntityHelper.createObject(
281: repositoryResult.get(i), listClass);
282: tempResult.add(entity);
283: } catch (Exception e) {
284: log.error("[CRUDMain.search] error", e);
285: }
286: }
287: }
288: setResult(tempResult);
289: ObjectContext.getInstance().refresh(this , "result");
290: }
291:
292: /**
293: * Create a new instance. It's the "C" of CRUD pattern.
294: *
295: * @throws SecurityException
296: * @throws NoSuchMethodException
297: * @throws IllegalArgumentException
298: * @throws InstantiationException
299: * @throws IllegalAccessException
300: * @throws InvocationTargetException
301: */
302: @Persistence(mode=PersistenceConstants.MODE_ATOMIC)
303: public Object create() throws SecurityException,
304: NoSuchMethodException, IllegalArgumentException,
305: InstantiationException, IllegalAccessException,
306: InvocationTargetException {
307: SchemaClass cls = ObjectContext.getInstance().getComponent(
308: SchemaManager.class).getClassInfo(
309: createClass.getSimpleName());
310: Class<?> entityClass = (Class<?>) cls.getFeature(
311: ViewAspect.ASPECT_NAME, ViewClassFeatures.ENTITY);
312: Object entityObject = EntityHelper.createObject(null,
313: entityClass);
314:
315: Object createInstance = ObjectContext.getInstance().getObject(
316: createClass, entityObject);
317:
318: if (createInstance instanceof CRUDEntity)
319: ((CRUDEntity<?>) createInstance).setBackObject(this );
320:
321: if (createInstance instanceof CRUDInstance) {
322: ((CRUDInstance<?>) createInstance)
323: .setMode(CRUDInstance.MODE_CREATE);
324: ((CRUDInstance<?>) createInstance).onCreate();
325: }
326:
327: ObjectContext.getInstance().show(createInstance);
328: return createInstance;
329: }
330:
331: /**
332: * Read the selected instance. It's the "R" of CRUD pattern.
333: *
334: * @throws InstantiationException
335: * @throws IllegalAccessException
336: * @throws IllegalArgumentException
337: * @throws InvocationTargetException
338: */
339: @Persistence(mode=PersistenceConstants.MODE_ATOMIC)
340: public Object read() throws InstantiationException,
341: IllegalAccessException, IllegalArgumentException,
342: InvocationTargetException {
343: Object selectedObj = getOnlyOneSelectedItem(getSelection());
344:
345: if (selectedObj == null)
346: return null;
347:
348: Object loadedObject = loadObjectDetails(selectedObj);
349:
350: Object readInstance = ObjectContext.getInstance().getObject(
351: readClass, loadedObject);
352:
353: if (readInstance instanceof CRUDEntity)
354: ((CRUDEntity<?>) readInstance).setBackObject(this );
355:
356: if (readInstance instanceof CRUDInstance) {
357: ((CRUDInstance<?>) readInstance)
358: .setMode(CRUDInstance.MODE_READ);
359: ((CRUDInstance<?>) readInstance).onRead();
360: }
361:
362: ObjectContext.getInstance().show(readInstance);
363: return readInstance;
364: }
365:
366: /**
367: * Update the selected instance. It's the "U" of CRUD pattern.
368: *
369: * @throws InstantiationException
370: * @throws IllegalAccessException
371: * @throws IllegalArgumentException
372: * @throws InvocationTargetException
373: */
374: @Persistence(mode=PersistenceConstants.MODE_ATOMIC)
375: public Object update() throws InstantiationException,
376: IllegalAccessException, IllegalArgumentException,
377: InvocationTargetException {
378: Object selectedObj = getOnlyOneSelectedItem(getSelection());
379:
380: if (selectedObj == null)
381: return null;
382:
383: Object loadedObject = loadObjectDetails(selectedObj);
384:
385: Object updateInstance = ObjectContext.getInstance().getObject(
386: updateClass, loadedObject);
387:
388: if (updateInstance instanceof CRUDEntity)
389: ((CRUDEntity<?>) updateInstance).setBackObject(this );
390:
391: if (updateInstance instanceof CRUDInstance) {
392: ((CRUDInstance<?>) updateInstance)
393: .setMode(CRUDInstance.MODE_UPDATE);
394: ((CRUDInstance<?>) updateInstance).onUpdate();
395: }
396:
397: ObjectContext.getInstance().show(updateInstance);
398: return updateInstance;
399: }
400:
401: protected Object loadObjectDetails(Object iObj) {
402: return ObjectContext.getInstance().getContextComponent(
403: PersistenceAspect.class).loadObject(
404: ((ComposedEntity<?>) iObj).getEntity(),
405: PersistenceAspect.FULL_MODE_LOADING,
406: PersistenceAspect.STRATEGY_DETACHING);
407: }
408:
409: protected Object getOnlyOneSelectedItem(Object[] iSelection) {
410: if (iSelection == null || iSelection.length != 1) {
411: MessageOk dialog = new MessageOk("crud",
412: "$Message.Information");
413: dialog.setIcon("information.gif");
414: dialog.setMessage("$CRUDMain.selectOnlyOne.error");
415: ObjectContext.getInstance().show(dialog);
416: return null;
417: }
418:
419: return iSelection[0];
420: }
421:
422: /**
423: * Delete the current selection allowing the multi-selection of items. It's the "D" of CRUD pattern.
424: *
425: * @throws InstantiationException
426: * @throws IllegalAccessException
427: */
428:
429: public void delete() throws InstantiationException,
430: IllegalAccessException {
431: Object[] selection = getSelection();
432: MessageText msg = null;
433: if (selection == null || selection.length == 0) {
434: msg = new MessageOk("delete", "Information", this );
435: msg.setMessage("$CRUDMain.selectAtLeastOne.error");
436: msg.setIcon("information.gif");
437: } else {
438: msg = new MessageYesNo("delete", "Warning", this );
439: msg.setMessage("$CRUDMain.delete.confirm");
440: msg.setIcon("question.gif");
441: }
442:
443: ObjectContext.getInstance().show(msg);
444: }
445:
446: public void responseMessage(Message iMessage, Object iResponse) {
447: if (iResponse == null || !((Boolean) iResponse))
448: // USER SELECT NO
449: return;
450:
451: if (iMessage.getId().equals(QUERY_ALL_MESSAGE)) {
452: queryRequest.setRangeFrom(0, queryRequest.getTotalItems());
453: executeQuery();
454: return;
455: }
456:
457: Object[] selection = getSelection();
458: if (selection != null) {
459: Object selectedInstance;
460: ArrayList<Object> selectedInstances = new ArrayList<Object>();
461: // BUILD THE ARRAY OF OBJECT TO DELETE
462: for (int i = 0; i < selection.length; ++i) {
463: selectedInstance = selection[i];
464:
465: if (selection[i] instanceof ComposedEntity)
466: // GET THE COMPOSED SELECTION
467: selectedInstance = ((ComposedEntity<?>) selectedInstance)
468: .getEntity();
469:
470: selectedInstances.add(selectedInstance);
471: }
472:
473: ObjectContext.getInstance().setContextComponent(
474: PersistenceAspect.class,
475: ObjectContext.getInstance().getComponent(
476: "TxPersistenceAspect"));
477:
478: // DELETE THE PERSISTENT OBJECTS
479: ObjectContext.getInstance().getComponent(
480: PersistenceAspect.class).deleteObjects(
481: selectedInstances.toArray());
482:
483: // RE-EXECUTE THE QUERY TO UPDATE VIEW
484: search();
485: }
486: }
487:
488: /**
489: * Enable/Disable actions, based on current user profiling if any
490: */
491: @Persistence(mode=PersistenceConstants.MODE_NOTX)
492: @Override
493: public void setSelection(Object[] iSelectedObjects) {
494: super .setSelection(iSelectedObjects);
495:
496: if (iSelectedObjects == null || iSelectedObjects.length == 0) {
497: ObjectContext.getInstance().setActionFeature(this ,
498: ViewAspect.ASPECT_NAME, "update",
499: ViewElementFeatures.ENABLED, Boolean.FALSE);
500:
501: ObjectContext.getInstance().setActionFeature(this ,
502: ViewAspect.ASPECT_NAME, "delete",
503: ViewElementFeatures.ENABLED, Boolean.FALSE);
504: } else {
505: ObjectContext.getInstance().setActionFeature(this ,
506: ViewAspect.ASPECT_NAME, "delete",
507: ViewElementFeatures.ENABLED,
508: iSelectedObjects.length > 0);
509: ObjectContext.getInstance().setActionFeature(this ,
510: ViewAspect.ASPECT_NAME, "update",
511: ViewElementFeatures.ENABLED,
512: iSelectedObjects.length == 1);
513: }
514:
515: ObjectContext.getInstance().refresh(this , "result");
516: }
517:
518: public void selectAll() {
519: Object[] sel = new Object[((Collection<?>) getResult()).size()];
520: ((Collection<?>) getResult()).toArray(sel);
521: setSelection(sel);
522: ObjectContext.getInstance().refresh(this , "result");
523: }
524:
525: public void deselectAll() {
526: setSelection(null);
527: ObjectContext.getInstance().refresh(this , "result");
528: }
529:
530: public int getPageElements() {
531: return pageElements;
532: }
533:
534: public void setPageElements(int pageElements) {
535: this .pageElements = pageElements;
536: }
537:
538: public Object getBackObject() {
539: return backObject;
540: }
541:
542: public void setBackObject(Object backObject) {
543: this .backObject = backObject;
544: }
545:
546: public void onClassLoading(Class<?> iClass) {
547: if (iClass.getName().equals(createClass.getName()))
548: createClass = iClass;
549: if (iClass.getName().equals(readClass.getName()))
550: readClass = iClass;
551: if (iClass.getName().equals(updateClass.getName()))
552: updateClass = iClass;
553: if (iClass.getName().equals(listClass.getName()))
554: listClass = iClass;
555: }
556:
557: protected PersistenceAspect getPersistenceAspect() {
558: return ObjectContext.getInstance().getContextComponent(
559: PersistenceAspect.class);
560: }
561: }
|