Java tutorial
/** * Exhibit A - UIRF Open-source Based Public Software License. * * The contents of this file are subject to the UIRF Open-source Based Public * Software License(the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * openelis.uhl.uiowa.edu * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for * the specific language governing rights and limitations under the License. * * The Original Code is OpenELIS code. * * The Initial Developer of the Original Code is The University of Iowa. * Portions created by The University of Iowa are Copyright 2006-2008. All * Rights Reserved. * * Contributor(s): ______________________________________. * * Alternatively, the contents of this file marked "Separately-Licensed" may be * used under the terms of a UIRF Software license ("UIRF Software License"), in * which case the provisions of a UIRF Software License are applicable instead * of those above. */ package org.openelis.modules.attachment.client; import static org.openelis.modules.main.client.Logger.*; import static org.openelis.ui.screen.Screen.ShortKeys.*; import static org.openelis.ui.screen.State.*; import java.util.ArrayList; import java.util.HashMap; import java.util.logging.Level; import org.openelis.cache.UserCache; import org.openelis.constants.Messages; import org.openelis.domain.AttachmentDO; import org.openelis.domain.AttachmentIssueViewDO; import org.openelis.manager.AttachmentManager; import org.openelis.meta.AttachmentMeta; import org.openelis.modules.attachment.client.AttachmentIssueEvent.Action; import org.openelis.ui.common.DataBaseUtil; import org.openelis.ui.common.EntityLockedException; import org.openelis.ui.common.data.Query; import org.openelis.ui.common.data.QueryData; import org.openelis.ui.event.DataChangeEvent; import org.openelis.ui.event.StateChangeEvent; import org.openelis.ui.screen.AsyncCallbackUI; import org.openelis.ui.screen.Screen; import org.openelis.ui.screen.ScreenHandler; import org.openelis.ui.screen.State; import org.openelis.ui.widget.Button; import org.openelis.ui.widget.CheckBox; import org.openelis.ui.widget.TextBox; import org.openelis.ui.widget.table.Row; import org.openelis.ui.widget.table.Table; import org.openelis.ui.widget.table.event.BeforeCellEditedEvent; import org.openelis.ui.widget.table.event.BeforeCellEditedHandler; import org.openelis.ui.widget.table.event.CellEditedEvent; import org.openelis.ui.widget.table.event.CellEditedHandler; import org.openelis.ui.widget.table.event.UnselectionEvent; import org.openelis.ui.widget.table.event.UnselectionHandler; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.logical.shared.BeforeSelectionEvent; import com.google.gwt.event.logical.shared.BeforeSelectionHandler; import com.google.gwt.event.shared.EventBus; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.uibinder.client.UiTemplate; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Widget; public abstract class TRFTabUI extends Screen { @UiTemplate("TRFTab.ui.xml") interface TRFTabUIBinder extends UiBinder<Widget, TRFTabUI> { }; private static TRFTabUIBinder uiBinder = GWT.create(TRFTabUIBinder.class); @UiField protected Button queryButton, updateButton, commitButton, abortButton, refreshButton, unlockAllButton; @UiField protected TextBox<String> description; @UiField protected CheckBox unattached; @UiField protected Table table; protected AsyncCallbackUI<ArrayList<AttachmentManager>> queryCall; protected Screen parentScreen, screen1; protected EventBus parentBus; protected int trfShownRow; protected boolean query, refresh; protected HashMap<Integer, AttachmentManager> managerMap; protected HashMap<Integer, AttachmentIssueViewDO> issueMap; protected static int MAX_ATTACHMENTS = 1000; public TRFTabUI(Screen parentScreen) { this.parentScreen = parentScreen; this.parentBus = parentScreen.getEventBus(); initWidget(uiBinder.createAndBindUi(this)); initialize(); } protected void initialize() { screen1 = this; // // button panel buttons // addStateChangeHandler(new StateChangeEvent.Handler() { public void onStateChange(StateChangeEvent event) { queryButton.setEnabled(isState(QUERY, DEFAULT, DISPLAY)); if (isState(QUERY)) { queryButton.lock(); queryButton.setPressed(true); } } }); addShortcut(queryButton, 'q', CTRL); addStateChangeHandler(new StateChangeEvent.Handler() { public void onStateChange(StateChangeEvent event) { updateButton.setEnabled(isState(UPDATE, DISPLAY)); if (isState(UPDATE)) { updateButton.lock(); updateButton.setPressed(true); } } }); addShortcut(updateButton, 'u', CTRL); addStateChangeHandler(new StateChangeEvent.Handler() { public void onStateChange(StateChangeEvent event) { commitButton.setEnabled(isState(QUERY, UPDATE)); } }); addShortcut(commitButton, 'm', CTRL); addStateChangeHandler(new StateChangeEvent.Handler() { public void onStateChange(StateChangeEvent event) { abortButton.setEnabled(isState(QUERY, UPDATE)); } }); addShortcut(abortButton, 'o', CTRL); addScreenHandler(description, AttachmentMeta.getDescription(), new ScreenHandler<Object>() { public void onStateChange(StateChangeEvent event) { description.setEnabled(isState(QUERY)); } public Widget onTab(boolean forward) { return forward ? unattached : unlockAllButton; } }); addScreenHandler(unattached, "unattached", new ScreenHandler<Object>() { public void onStateChange(StateChangeEvent event) { unattached.setEnabled(isState(QUERY)); } public Widget onTab(boolean forward) { return forward ? table : description; } }); addScreenHandler(table, "table", new ScreenHandler<ArrayList<Row>>() { public void onDataChange(DataChangeEvent<ArrayList<Row>> event) { loadTable(null); } public void onStateChange(StateChangeEvent event) { table.setEnabled(true); } public Widget onTab(boolean forward) { return forward ? refreshButton : unattached; } }); table.addUnselectionHandler(new UnselectionHandler<Integer>() { public void onUnselection(UnselectionEvent<Integer> event) { /* * the selected row is not allowed to be unselected in Update * state because its manager is locked */ if (isState(UPDATE)) event.cancel(); } }); table.addBeforeSelectionHandler(new BeforeSelectionHandler<Integer>() { public void onBeforeSelection(BeforeSelectionEvent<Integer> event) { /* * no other row is allowed to be selected in Update state * because the selected row's manager is locked */ if (isState(UPDATE)) event.cancel(); } }); /* * screen fields and widgets */ table.addBeforeCellEditedHandler(new BeforeCellEditedHandler() { @Override public void onBeforeCellEdited(BeforeCellEditedEvent event) { int r, c; Object val; r = event.getRow(); c = event.getCol(); if (c == 0) { val = table.getValueAt(r, c); if ((!isState(DISPLAY) || "Y".equals(val)) || !lock(r)) event.cancel(); /* * if this row got selected by clicking in the first column * (the checkbox) instead of some other one, select the row * whose TRF was shown the most recently, if any, otherwise * unselect this row; the TRF is not shown if the first * column is clicked, so if this row stayed selected, the * user may assume that the TRF was shown and may not click * in some other column to see it */ if (trfShownRow != r) { if (trfShownRow >= 0) table.selectRowAt(trfShownRow); else table.unselectRowAt(r); } return; } /* * this makes sure that the attachment for a row is not shown * more than once if different cells of the row are clicked one * after the other; displayAttachment is called from here * instead of from SelectionHandler because the TRF is not shown * if the user clicks in the first column (the checkbox) and the * column can't be known in SelectionHandler */ if (trfShownRow != r) { displayAttachment(table.getRowAt(r)); trfShownRow = r; } if (c != 3 || !isState(UPDATE)) event.cancel(); } }); table.addCellEditedHandler(new CellEditedHandler() { @Override public void onCellUpdated(CellEditedEvent event) { int r, c; Integer id; String val; AttachmentIssueViewDO ai; AttachmentDO a; AttachmentManager am; r = event.getRow(); c = event.getCol(); val = (String) table.getValueAt(r, c); id = table.getRowAt(r).getData(); am = managerMap.get(id); ai = issueMap.get(id); switch (c) { case 3: if (ai == null) { /* * if there's no attachment issue for this row's * attachment and the user entered some text, create * a new issue */ if (!DataBaseUtil.isEmpty(val)) { ai = new AttachmentIssueViewDO(); a = am.getAttachment(); ai.setAttachmentId(a.getId()); ai.setAttachmentDescription(a.getDescription()); ai.setText(val); issueMap.put(id, ai); table.getRowAt(r).setData(a.getId()); } } else { ai.setText(val); } break; } } }); addScreenHandler(refreshButton, "refreshButton", new ScreenHandler<ArrayList<Row>>() { public void onStateChange(StateChangeEvent event) { refreshButton.setEnabled(isState(DEFAULT, DISPLAY)); } public Widget onTab(boolean forward) { return forward ? unlockAllButton : table; } }); addScreenHandler(unlockAllButton, "unlockAllButton", new ScreenHandler<ArrayList<Row>>() { public void onStateChange(StateChangeEvent event) { unlockAllButton.setEnabled(isState(DISPLAY)); } public Widget onTab(boolean forward) { return forward ? description : refreshButton; } }); parentBus.addHandler(AttachmentIssueEvent.getType(), new AttachmentIssueEvent.Handler() { @Override public void onAttachmentIssue(AttachmentIssueEvent event) { Query q; if (parentScreen != event.getSource()) return; /* * the tab fires events to the main screen to request it to do * various operations e.g. fetch, lock, unlock etc; that's * because the main screen maintains the data structures used by * all tabs; if the operation was successful, the main screen * fires an event to let the tab know; that event is handled * here; the tab can find out if this event was fired to respond * to a previous event fired by it or some other tab by checking * if it's the event's "originalSource"; it can then perform * some operation or ignore the event */ issueMap = event.getIssueMap(); switch (event.getAction()) { case FETCH: if (screen1 == event.getOriginalSource()) { if (query) { /* * the user wants to query; set the screen in * Query state */ query = false; description.setFocus(true); setState(QUERY); parentScreen.setDone(Messages.get().gen_enterFieldsToQuery()); } else if (refresh) { /* * the user wants to refresh the list of * attachments; fetch unattached attachments for * the default pattern */ refresh = false; description.setValue(getPattern()); unattached.setValue("Y"); managerMap = null; q = new Query(); q.setFields(getQueryFields()); q.setRowsPerPage(MAX_ATTACHMENTS); executeQuery(q); } } else { /* * refresh the issues in the table because they were * fetched from the database by some other tab */ query = false; refresh = false; refreshIssues(); } break; case ADD: case UPDATE: case DELETE: /* * refresh the issues in the table because some were * added, updated or deleted */ refreshIssues(); setState(DISPLAY); break; case LOCK: if (screen1 == event.getOriginalSource()) { /* * an issue is locked to be updated; refresh the tab * to show the latest data */ refreshSelectedRow(); setState(UPDATE); table.startEditing(table.getSelectedRow(), 3); } break; case UNLOCK: if (screen1 == event.getOriginalSource()) { /* * an issue was unlocked; refresh the tab to show * the previously deleted or changed issues again */ refreshIssues(); setState(DISPLAY); } break; } } }); } /** * Overridden to specify the pattern for the TRFs of a particular domain */ public abstract String getPattern(); /** * Returns a list of fields that will be used in the query for fetching * attachments; it's overridden because the widgets used in the query are * not put in query mode; that's because they lose their previous values in * query mode and that's not the desired behavior here */ public ArrayList<QueryData> getQueryFields() { QueryData field; ArrayList<QueryData> fields; fields = new ArrayList<QueryData>(); field = (QueryData) description.getQuery(); if (field != null) { field.setKey(AttachmentMeta.getDescription()); fields.add(field); } return fields; } public Validation validate() { Row row; AttachmentIssueViewDO data; Validation validation; table.clearEndUserExceptions(); validation = super.validate(); if (isState(UPDATE)) { /* * don't allow the user to commit if there's no attachment issue for * the selected row; this can happen if the attachment didn't have * an issue to begin with and the user never entered any any text in * the row */ row = table.getRowAt(table.getSelectedRow()); data = issueMap.get(row.getData()); if (data == null) { table.addException(table.getSelectedRow(), 3, new Exception(Messages.get().gen_fieldRequiredException())); validation.setStatus(Validation.Status.ERRORS); } } return validation; } /** * Executes a query to fetch unattached attachments */ public void fetchUnattached() { refresh = true; query = false; fireAttachmentIssue(AttachmentIssueEvent.Action.FETCH, null); } /** * If a row is selected returns its manager; otherwise, returns null */ public AttachmentManager getSelected() { Row row; row = table.getRowAt(table.getSelectedRow()); if (row == null) return null; return managerMap.get((Integer) row.getData()); } /** * Returns true if the checkbox under "Lock" is checked for any attachment * in the table; false otherwise */ public boolean isAttachmentsLocked() { boolean reserved; Row row; reserved = false; for (int i = 0; i < table.getRowCount(); i++) { row = table.getRowAt(i); if ("Y".equals(row.getCell(0))) { reserved = true; break; } } return reserved; } /** * Returns the tab's current state */ public State getState() { return state; } /** * Puts the screen in query state; if no attachments are locked */ @UiHandler("queryButton") protected void query(ClickEvent event) { if (isAttachmentsLocked()) { Window.alert(Messages.get().trfAttachment_firstUnlockAll()); } else { query = true; refresh = false; fireAttachmentIssue(AttachmentIssueEvent.Action.FETCH, null); } } /** * Puts the screen in update state and locks the selected attachment */ @UiHandler("updateButton") protected void update(ClickEvent event) { int r; Integer id; r = table.getSelectedRow(); if (r < 0) { Window.alert(Messages.get().trfAttachment_selectAttachment()); return; } id = table.getRowAt(r).getData(); fireAttachmentIssue(AttachmentIssueEvent.Action.LOCK, id); } /** * Validates the data on the screen and based on the current state, and * calls the service method to commit the data on the screen, to the * database. Shows any errors/warnings encountered during the commit, * otherwise refreshes the tree with the committed data. */ @UiHandler("commitButton") protected void commit(ClickEvent event) { finishEditing(); if (validate().getStatus() == Validation.Status.ERRORS) { parentScreen.setError(Messages.get().gen_correctErrors()); return; } switch (state) { case QUERY: commitQuery(); break; case UPDATE: commitUpdate(); break; } } /** * Creates query fields from the data on the screen and executes a query to * return a list of attachments */ protected void commitQuery() { Query query; managerMap = null; query = new Query(); query.setFields(getQueryFields()); query.setRowsPerPage(MAX_ATTACHMENTS); executeQuery(query); } /** * Commits the data on the screen to the database; shows any errors/warnings * encountered during the commit, otherwise loads the screen with the * committed data; if the checkbox for the attachment was checked i.e. it * was locked by this user before Update was clicked, tries to lock it * again, because update removes the lock in the back-end */ protected void commitUpdate() { Row row; AttachmentIssueViewDO data; AttachmentIssueEvent.Action action; row = table.getRowAt(table.getSelectedRow()); data = issueMap.get(row.getData()); if (data.getText() == null) { action = AttachmentIssueEvent.Action.DELETE; } else { if (data.getId() == null) action = AttachmentIssueEvent.Action.ADD; else action = AttachmentIssueEvent.Action.UPDATE; } fireAttachmentIssue(action, data.getAttachmentId()); } /** * Reverts any changes made to the data on the screen and disables editing * of the widgets; if the checkbox for the attachment was checked i.e. it * was locked by this user before Update was clicked, tries to lock it * again, because the unlock removes the lock in the back-end */ @UiHandler("abortButton") protected void abort(ClickEvent event) { Row row; finishEditing(); clearErrors(); if (isState(QUERY)) { parentScreen.setBusy(Messages.get().gen_cancelChanges()); if (table.getRowCount() > 0) setState(DISPLAY); else setState(DEFAULT); parentScreen.setDone(Messages.get().gen_queryAborted()); } else if (isState(UPDATE)) { row = table.getRowAt(table.getSelectedRow()); fireAttachmentIssue(AttachmentIssueEvent.Action.UNLOCK, (Integer) row.getData()); } } /** * Executes the query for the latest unattached attachments for a particular * domain and reloads the table with the returns data; doesn't execute the * query if any attachment currently in the table is locked */ @UiHandler("refreshButton") protected void refresh(ClickEvent event) { if (isAttachmentsLocked()) { Window.alert(Messages.get().trfAttachment_firstUnlockAll()); } else { refresh = true; query = false; fireAttachmentIssue(AttachmentIssueEvent.Action.FETCH, null); } } /** * Unlocks all the locked attachments i.e. the ones whose checkbox is * checked and refreshes the rows with the latest data */ @UiHandler("unlockAllButton") protected void unlockAll(ClickEvent event) { Row row; Integer id; AttachmentManager am; for (int i = 0; i < table.getRowCount(); i++) { row = table.getRowAt(i); id = (Integer) row.getData(); if ("Y".equals(row.getCell(0))) { try { am = AttachmentService.get().unlock(id); managerMap.put(id, am); refreshRow(i, "N", null, id); } catch (Exception e) { Window.alert(e.getMessage()); logger.log(Level.SEVERE, e.getMessage(), e); } } } setState(DISPLAY); parentScreen.clearStatus(); } /** * Uses the passed query to fetch attachments and refreshes the screen with * the returned data; if the checkbox "unattached" is checked, fetches only * unattached attachments; otherwise fetches all possible attachments * complying with the query */ private void executeQuery(final Query query) { trfShownRow = -1; parentScreen.setBusy(Messages.get().gen_fetching()); if (queryCall == null) { queryCall = new AsyncCallbackUI<ArrayList<AttachmentManager>>() { public void success(ArrayList<AttachmentManager> result) { loadTable(null); /* * this map is used to link a table row with the manager * containing the attachment that it's showing */ if (managerMap == null) managerMap = new HashMap<Integer, AttachmentManager>(); for (AttachmentManager am : result) managerMap.put(am.getAttachment().getId(), am); setState(DISPLAY); loadTable(result); refreshIssues(); parentScreen.clearStatus(); } public void notFound() { setState(DEFAULT); loadTable(null); parentScreen.setDone(Messages.get().gen_noRecordsFound()); } public void failure(Throwable e) { Window.alert("Error: Data Entry TRF Attachment call query failed; " + e.getMessage()); logger.log(Level.SEVERE, e.getMessage(), e); parentScreen.setError(Messages.get().gen_queryFailed()); } }; } if ("Y".equals(unattached.getValue())) AttachmentService.get().fetchByQueryUnattached(query.getFields(), query.getPage() * query.getRowsPerPage(), query.getRowsPerPage(), queryCall); else AttachmentService.get().fetchByQuery(query.getFields(), query.getPage() * query.getRowsPerPage(), query.getRowsPerPage(), queryCall); } /** * Locks the attachment showing on the row at the passed index; the * attachment is locked for a longer duration than the one for update */ private boolean lock(int index) { Integer id; AttachmentManager am; id = table.getRowAt(index).getData(); /* * this attachment is currently not locked and the user is trying to * lock it; show "Locked by xyz" under status, where "xyz" is the name * of the user who has it locked under "Status"; if the attachment has * already been attached, show "Attached; Locked by xyz" under "Status" */ try { am = AttachmentService.get().fetchForReserve(id); managerMap.put(id, am); table.setValueAt(index, 1, getStatus(UserCache.getPermission().getLoginName(), am)); } catch (EntityLockedException e) { table.setValueAt(index, 1, getStatus(e.getUserName(), null)); return false; } catch (Exception e) { Window.alert(e.getMessage()); logger.log(Level.SEVERE, e.getMessage(), e); return false; } return true; } /** * Creates and returns the message to be shown under "Status"; if the passed * manager has any attachment items, the message is * "Attached: Locked by xyz" where "xyz" is the passed username; otherwise * the message is "Locked by xyz" * */ private String getStatus(String userName, AttachmentManager am) { String attached; if (am != null && am.item.count() > 0) attached = Messages.get().trfAttachment_attached(); else attached = null; return DataBaseUtil.concatWithSeparator(Messages.get().trfAttachment_lockedBy(userName), "; ", attached); } /** * Loads the table with data in the passed managers */ private void loadTable(ArrayList<AttachmentManager> ams) { Row row; AttachmentDO data; if (ams == null) { table.setModel(null); return; } for (AttachmentManager am : ams) { row = new Row(5); data = am.getAttachment(); row.setCell(0, "N"); row.setCell(2, data.getDescription()); row.setCell(4, data.getCreatedDate()); row.setData(data.getId()); table.addRow(row); } } /** * Refreshes the row at the passed index with passed arguments; the values * in the first two columns are set to "lock" and "status" respectively; the * values in the other columns are set as the corresponding fields in the * attachment issue whose attachment id is "attachmentId" */ private void refreshRow(int index, String lock, String status, Integer attachmentId) { Row row; AttachmentDO a; AttachmentIssueViewDO ai; row = table.getRowAt(index); a = managerMap.get(attachmentId).getAttachment(); ai = issueMap.get(attachmentId); table.setValueAt(index, 0, lock); table.setValueAt(index, 1, status); table.setValueAt(index, 2, a.getDescription()); table.setValueAt(index, 3, ai != null ? ai.getText() : null); table.setValueAt(index, 4, a.getCreatedDate()); row.setData(a.getId()); } /** * This method is called when commit or abort is called for an attachment * that was locked for update; both of these operations release the lock in * the back-end; so if the attachment was locked by checking the checkbox * before clicking "Update", this method tries to obtain the lock again, so * that the user can keep the record locked until it's explicitly unlocked * by clicking "Unlock All"; refreshes the row with the latest data * regardless of whether the attachment could be locked */ private void refreshSelectedRow() { int r; Integer id; Row row; r = table.getSelectedRow(); row = table.getRowAt(r); id = row.getData(); refreshRow(r, (String) row.getCell(0), (String) row.getCell(1), id); } /** * Refreshes the rows in the table to show the latest data in the attachment * issues for the attachments in the table */ private void refreshIssues() { Integer id; AttachmentIssueViewDO data; if (issueMap != null) { for (int i = 0; i < table.getRowCount(); i++) { id = table.getRowAt(i).getData(); data = issueMap.get(id); table.setValueAt(i, 3, data != null ? data.getText() : null); } } } /** * Opens the file linked to the attachment showing on the passed row; if * "name" is null or if it's different from the previous time this method * was called then the file is opened in a new window, otherwise it's opened * in the same window as before. */ private void displayAttachment(Row row) { Integer id; id = (Integer) row.getData(); if (id != null) parentBus.fireEventFromSource(new DisplayAttachmentEvent(id, true), screen1); } /** * Fires an AttachmentIssueEvent to the main screen to request it to do * various operations like fetch, lock, unlock etc. for an attachment issue; * the operation is specified by "action" and "attachmentId" is used by the * main screen to find the attachment issue */ private void fireAttachmentIssue(Action action, Integer attachmentId) { parentBus.fireEventFromSource(new AttachmentIssueEvent(action, attachmentId, null, null, screen1), screen1); } }