Il metodo view(CqRecord) legge ora la proprietà LEGAL_ACTIONS del record oltre alla proprietà ALL_FIELD_VALUES. Il valore della proprietà LEGAL_ACTIONS è rappresentato da un elenco di proxy azione per le azioni legalmente applicabili al record nello stato corrente. Questo elenco viene utilizzato per popolare un controllo di tipo casella combinata, da cui l'utente può selezionare un'azione prima di fare clic sul pulsante di modifica. Alcuni tipi di azioni, come ad esempio SUBMIT e RECORD_SCRIPT_ALIAS non sono supportati da questo esempio e non vengono pertanto aggiunti al controllo di tipo casella combinata anche se presente nella serie di azioni legali.
public JFrame view(CqRecord selected) { try { final CqRecord record = (CqRecord)selected.doReadProperties(RECORD_PROPERTIES); final JButton start = new JButton("Edit"); final JComboBox choices = new JComboBox(); for (CqAction a: record.getLegalActions()) { if (a.getType() != CqAction.Type.IMPORT && a.getType() != CqAction.Type.SUBMIT && a.getType() != CqAction.Type.BASE && a.getType() != CqAction.Type.RECORD_SCRIPT_ALIAS) choices.addItem(a); } final JButton add = new JButton("Attach"); final JButton remove = new JButton("Detach"); final ViewRecord.RecordFrame frame = showRecord("View: ", record, choices.getItemCount()==0? null: new JComponent[]{choices, start, add, remove});
EditView.Viewer supporta anche un pulsante Allega e un pulsante Dissocia per l'aggiunta e la rimozione dei file allegati di un campo allegato.
Il pulsante Allega presenta all'utente una finestra di dialogo di selezione file Swing standard e consente la selezione del file da allegare. Per allegare il file al record, viene utilizzato CqAttachment.doCreateAttachment(), inoltrando il nome del file selezionato dall'utente. Prima di poter richiamare questo metodo, è necessario creare un proxy CqAttachment che indirizzi all'ubicazione appropriata. La cartella nella quale si troverà la risorsa allegato è il valore del campo al quale è allegata - si tratta del campo al momento selezionato nella vista record e, quindi, viene ottenuto dall'oggetto frame restituito da ViewRecord.showRecord. Un nome univoco per la risorsa allegato viene generato dall'ora corrente del giorno. Questo nome è semplicemente un segnaposto poiché doCreateAttachment() è libero di modificare il nome della risorsa in base alle esigenze.
add.addActionListener(new ActionListener(){ /** * Richiede all'utente il nome di un file da allegare al * campo selezionato. Se fornito in questo modo, il contenuto del * file viene letto nel database ClearQuest come allegato. */ public void actionPerformed(ActionEvent arg0) { if (!ViewRecord.isAttachmentList(frame.m_fields, frame.m_table.getSelectedRow())) return; JFileChooser chooser = new JFileChooser(); if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) { try { String filename = chooser.getSelectedFile().getAbsolutePath(); CqFieldValue field = frame.m_fields.get(frame.m_table.getSelectedRow()); StpLocation aLoc = (StpLocation) ((StpFolder)field.getValue()).location() .child("new" + System.currentTimeMillis()); CqAttachment attachment = record.cqProvider().cqAttachment(aLoc); attachment = attachment.doCreateAttachment(filename, null, CqProvider.DELIVER); JOptionPane.showMessageDialog(frame, "Added '" + filename + "' as " + attachment); frame.dispose(); view(record); } catch(Throwable t) { Utilities.exception(frame, "Add Attachment", t);} }}});
Il pulsante Dissocia utilizza ViewRecord.selectAttachment per ottenere dall'utente l'identità dell'allegato da rimuovere sotto forma di proxy CqAttachment. Il metodo doUnbindAll di questo proxy viene quindi richiamato per rimuovere l'allegato dal database.
remove.addActionListener(new ActionListener(){ /** * Consente all'utente di selezionare un allegato associato al * campo selezionato. Se l'utente esegue la selezione, tale allegato * viene eliminato. */ public void actionPerformed(ActionEvent arg0) { if (!ViewRecord.isAttachmentList(frame.m_fields, frame.m_table.getSelectedRow())) return; try { CqAttachment attachment = ViewRecord.selectAttachment (frame, frame.m_fields, frame.m_table.getSelectedRow(), "Remove"); if (attachment != null) { attachment.doUnbindAll(null); frame.dispose(); view(record); } } catch(Throwable t) { Utilities.exception(frame, "Remove Attachment", t);} } } );
Il pulsante Modifica recupera dalla casella combinata il proxy CqAction selezionato e inoltra questo e il proxy record per il programma di visualizzazione corrente al metodo di modifica, che avvierà effettivamente la modifica del record. Se il CqAction.Type dell'azione selezionata è DUPLICATE, è necessario che l'ID del record duplicato venga fornito con l'azione. Questo valore è richiesto dall'utente e viene posizionato nella mappa di argomenti dell'azione come valore dell'argomento denominato original.
start.addActionListener(new ActionListener(){ /** * Avvia la modifica del record utilizzando l'azione al momento * selezionata dalla casella combinata sulla finestra di dialogo di visualizzazione. */ public void actionPerformed(ActionEvent arg0) { CqAction action = (CqAction)choices.getSelectedItem(); try { if (action.getType() == CqAction.Type.DUPLICATE) { String id = JOptionPane.showInputDialog (frame, "Enter ID of duplicated record"); if (id == null) return; action.argumentMap(new Hashtable<String>()); action.argumentMap().put("original", record.cqProvider().cqRecord((StpLocation)record .location().parent().child(id))); } edit(record, action); frame.dispose(); } catch (Exception ex) { Utilities.exception(frame, "Duplicate Action", ex); } } }); return frame; } catch (WvcmException ex){ ex.printStackTrace(); } return null; }
Il metodo di modifica viene richiamato quando l'utente seleziona il pulsante Modifica nel programma di visualizzazione record. Viene inoltrato un proxy per il record da modificare e un proxy per l'azione con la quale è necessario eseguire la modifica.
Notare che il proxy azione, utilizzato per avviare un'azione ClearQuest, non deve definire alcuna proprietà - solo la relativa ubicazione e la mappa di argomenti (se necessario) devono essere definite. Per formare l'ubicazione di un'azione, è necessario conoscere il <name> (nome), il <record-type> (tipo di record) del record con il quale verrà utilizzata e il <database> e <db-set> in cui il record si trova.
L'ubicazione è quindi cq.action:<record-type>/<name>@<db-set>/<database>; un esempio è, cq.action:Defect/Assign@7.0.0/SAMPL
Utilizzando un nuovo proxy, CqRecord.doWriteProperties viene richiamato per iniziare l'operazione di modifica. L'unica proprietà che deve essere scritta è rappresentata dall'azione con la quale il record deve essere modificato. Il metodo doWriteProperties restituisce un proxy per il record aperto per la modifica e tale proxy conterrà le proprietà richieste nella PropertyRequest RECORD_PROPERTIES. È possibile ottenere le proprietà anche utilizzando record.doReadProperties (con la stessa PropertyRequest) dopo la restituzione di doWriteProperties, ma ciò risulterebbe meno efficace in quanto richiede un altro ciclo di andata e ritorno nel repository per gli stessi dati.
public void edit(CqRecord selected, CqAction action) { try { CqRecord record = m_provider.cqRecord(selected.stpLocation()); record = (CqRecord) record.setAction(action) .doWriteProperties(RECORD_PROPERTIES, CqProvider.HOLD); editRecord("Edit: ", record, selected); } catch (WvcmException ex){ Utilities.exception(null, "Start Action", ex); } }
Viewer.editRecord è simile a showRecord. La differenza primaria è che definisce TableModel.isCellEditable e TableModel.setValue.
JFrame editRecord(String title, final CqRecord record, final CqRecord selected) throws WvcmException { final JFrame frame = new JFrame(title + record.getUserFriendlyLocation()); JPanel panel = new JPanel(new BorderLayout()); JPanel buttons = new JPanel(new FlowLayout()); final JButton show = new JButton("View"); final JButton add = new JButton("Add"); final JButton remove = new JButton("Remove"); final JButton cancel = new JButton("Cancel"); final StpProperty.List<CqFieldValue>> fields = record.getAllFieldValues(); TableModel dataModel = new AbstractTableModel() { public int getColumnCount() { return fieldMetaProperties.length; } public int getRowCount() { return fields.size();} public String getColumnName(int col) { return fieldMetaProperties[col].getRoot().getName(); } public Object getValueAt(int row, int col) { try { return fields.get(row).getMetaProperty((MetaPropertyName<?>) fieldMetaProperties[col].getRoot()); } catch(Throwable ex) { if (ex instanceof StpException) { return ((StpException)ex).getStpReasonCode(); } else { String name = ex.getClass().getName(); return name.substring(name.lastIndexOf(".")+1); } } }
TableModel.isCellEditable restituisce il valore true solo per la colonna VALUE e solo se la riga appartiene a un campo il cui valore REQUIREDNESS non è READ_ONLY.
public boolean isCellEditable(int row, int col) { if (fieldMetaProperties[col].getRoot().equals(StpProperty.VALUE)) { CqFieldValue field = fields.get(row); try { return field.getRequiredness() != Requiredness.READ_ONLY; } catch (WvcmException ex){ Utilities.exception(frame, "Field Requiredness", ex); } } return false; }
TableModel.setValueAt imposta il nuovo valore campo nel proxy CqRecord associato alla visualizzazione. In primo luogo, la struttura CqFieldValue viene aggiornata utilizzando il relativo metodo initialize(). Questo metodo accetta una rappresentazione di stringa per la maggior parte dei tipi di campi e convertirà la stringa nei tipi appropriati di dati per il campo.
Una volta aggiornato CqFieldValue, quest'ultimo viene impostato nel proxy CqRecord associato a PropertyName per il campo. Questa fase è necessaria per fare in modo che il nuovo valore campo venga scritto nel database al momento del commit del record. Senza questa fase, il nuovo valore campo rimarrebbe solo nell'oggetto CqFieldValue, che fa parte della proprietà ALL_FIELD_VALUES. La proprietà ALL_FIELD_VALUES non è scrivibile, in questo modo la modifica non verrà mai scritta nel database. Copiando il CqFieldValue modificato direttamente nella voce proxy per il campo, tale campo diventa una proprietà aggiornata e il valore aggiornato verrà scritto nel database dal successivo metodo do eseguito sul proxy.
public void setValueAt(Object aValue, int row, int col) { if (fieldMetaProperties[col].getRoot().equals(StpProperty.VALUE)) { CqFieldValue<Object> field = fields.get(row); field.initialize(aValue); record.setFieldInfo(field.getFieldName(), field); } } private static final long serialVersionUID = 1L; }; final JTable table = new JTable(dataModel); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); show.setEnabled(false); add.setEnabled(false); remove.setEnabled(false); // Richiedere la notifica delle modifiche di selezione. ListSelectionModel rowSM = table.getSelectionModel(); rowSM.addListSelectionListener(new ListSelectionListener() { /** * Abilita i pulsanti nella finestra di dialogo in base al tipo di * campo selezionato nella tabella. */ public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()){ int[] selected = table.getSelectedRows(); show.setEnabled(false); add.setEnabled(false); remove.setEnabled(false); for (int i=0; i <selected.length; ++i) if (ViewRecord.getRecordReferencedAt(fields, selected[i]) != null) { show.setEnabled(true); } else if (ViewRecord.isAttachmentList(fields, selected[i])) { show.setEnabled(true); add.setEnabled(true); remove.setEnabled(true); } } } });
Il pulsante Visualizza è uguale al pulsante Visualizza in un ViewRecord.Viewer.
buttons.add(show); show.addActionListener(new ActionListener(){ /** * Richiama un metodo view sul valore del campo selezionato * nella tabella. */ public void actionPerformed(ActionEvent arg0) { int[] selected = table.getSelectedRows(); for (int i =0; i < selected.length; ++i) { int row = selected[i]; CqRecord record = ViewRecord.getRecordReferencedAt(fields, row); if (record != null) { view(record); } else if (ViewRecord.isAttachmentList(fields, row)) { view(ViewRecord.selectAttachment(frame, fields, row, "View")); } } } });
Il pulsante Consegna esegue il commit del record modificato nel database chiamando doWriteProperties() con l'opzione CqProvider.DELIVER. Dopo l'esito positivo della consegna, il programma di visualizzazione viene disattivato e un nuovo programma di visualizzazione viene attivato sul proxy originale. Poiché il metodo view() rilegge le proprietà dal database, questa nuova vista visualizzerà i valori aggiornati.
JButton deliver = new JButton("Deliver"); buttons.add(deliver); deliver.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { try { int mode = frame.getDefaultCloseOperation(); record.doWriteProperties(null, CqProvider.DELIVER); frame.dispose(); view(selected).setDefaultCloseOperation(mode); } catch (WvcmException ex){ Utilities.exception(frame, "Deliver failed", ex); } } });
Il pulsante Annulla abbandona l'operazione di modifica utilizzando il metodo doRevert del proxy record. Come per il pulsante Consegna, il programma di visualizzazione viene chiuso e viene creata un'istanza del nuovo programma di visualizzazione per il proxy originale.
buttons.add(cancel); cancel.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { try { int mode = frame.getDefaultCloseOperation(); record.doRevert(null); frame.dispose(); if (mode == JFrame.EXIT_ON_CLOSE) System.exit(0); view(selected).setDefaultCloseOperation(mode); } catch (WvcmException ex){ Utilities.exception(frame, "Cancel failed", ex); } } });
I pulsanti Allega e Dissocia sono uguali a quelli per ViewRecord.Viewer, ad eccezione dell'argomento di ordine consegna che è HOLD invece di DELIVER. Ciò rimanderà il commit dell'aggiunta o dell'eliminazione dell'allegato fino a quando non verrà eseguito il commit dell'intero record mediante il pulsante Consegna.
buttons.add(add); add.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showOpenDialog(frame); if (returnVal == JFileChooser.APPROVE_OPTION) { try { String filename = chooser.getSelectedFile().getAbsolutePath(); CqFieldValue field = fields.get(table.getSelectedRow()); StpLocation aLoc = (StpLocation) ((StpFolder)field.getValue()).location() .child("new" + System.currentTimeMillis()); CqAttachment attachment = record.cqProvider().cqAttachment(aLoc); attachment = attachment.doCreateAttachment(filename, null, CqProvider.HOLD); JOptionPane.showMessageDialog(frame, "Added '" + filename + "' as " + attachment + " (pending delivery)"); } catch(Throwable t) { Utilities.exception(frame, "Add Attachment", t);} }}}); buttons.add(remove); remove.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { try { CqAttachment attachment = ViewRecord.selectAttachment (frame, fields, table.getSelectedRow(), "Remove"); if (attachment != null) attachment.doUnbindAll(null); } catch(Throwable t) { Utilities.exception(frame, "Remove Attachment", t);} } } ); panel.add(new JScrollPane(table), BorderLayout.CENTER); panel.add(buttons, BorderLayout.SOUTH); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setContentPane(panel); frame.setBounds(300, 300, 600, 300); frame.setVisible(true); return frame; }
La GUI poteva eseguire una chiamata a record.doWriteProperties a ogni modifica di un campo, operazione che avrebbe fornito un immediato feedback all'utente, ma sarebbe anche potuta essere estremamente inefficace in base al protocollo di comunicazione tra il client e il server. La GUI poteva inoltre fornire un pulsante Aggiorna, che avrebbe causato la scrittura sul server di tutti gli aggiornamenti accumulati, senza realmente consegnarli. Questa operazione avrebbe fornito allo schema una opportunità per esaminare i valori e restituire gli errori. Queste modifiche vengono lasciate al lettore e l'approccio scelto dipenderà dall'utilizzo designato di questa applicazione.