view(CqRecord) メソッドは、ALL_FIELD_VALUES プロパティに加えて、レコードの LEGAL_ACTIONS プロパティを読み取るようになりました。LEGAL_ACTIONS プロパティの値は、現在の状態でレコードに対して正当に適用できるアクションのアクション プロキシのリストです。このリストは、コンボ ボックス コントロールを作成するために使用されます。ユーザーは編集ボタンをクリックする前に、この中からアクションを選択することができます。SUBMIT および RECORD_SCRIPT_ALIAS など、いくつかのタイプのアクションは、この例ではサポートされていません。したがってこれらは、たとえ正当なアクションのセット内に存在していても、コンボ ボックス コントロールに追加することはできません。
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 は、添付フィールドの添付ファイルを追加および除去するための [添付] および [削除] ボタンもサポートします。
[添付] ボタンによって、標準の Swing ファイル選択機能ダイアログがユーザーに提示され、添付するファイルを選択できるようになります。レコードへのファイルの添付は、CqAttachment.doCreateAttachment() を使用し、ユーザーが選択したファイルの名前をこれに渡すことで行われます。このメソッドを呼び出す前に、適切なロケーションをアドレス指定する CqAttachment プロキシが構成されている必要があります。添付 リソースが入るフォルダは、リソースが添付されるフィールドの値です。このフィールドは、レコード ビューで現在選択されているフィールドであり、ViewRecord.showRecord によって戻されるフレーム オブジェクトから取得されます。添付 リソースには、その日の現在時刻から生成された固有の名前が付けられます。doCreateAttachment() は、その必要性に応じてリソースの名前を自由に変更できるため、この名前は単なるプレースホルダーにすぎません。
add.addActionListener(new ActionListener(){
/**
* 選択されたフィールドに添付するファイルの名前を指定するように
* ユーザーにプロンプトが出されます。名前を指定すると、ファイルの内容が
* 添付ファイルとして ClearQuest データベースに読み取られます。
*/
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);}
}}});
[削除] ボタンは、ViewRecord.selectAttachment を使用して、除去する添付ファイルの識別情報を CqAttachment プロキシの形でユーザーから取得します。 次に、このプロキシの doUnbindAll メソッドが呼び出され、データベースから添付ファイルが削除されます。
remove.addActionListener(new ActionListener(){
/**
* 選択されたフィールドに関連する添付ファイルをユーザーが
* 選択できるようにします。ユーザーが添付ファイルを選択すると、
* そのファイルが削除されます。
*/
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);}
}
}
);
[編集] ボタンは、選択された CqAction プロキシをコンボ ボックスから取り出し、そのプロキシと現行のビューアのレコード プロキシを編集メソッドに渡します。これによりレコードの編集が実際に開始されます。選択されたアクションの CqAction.Type が DUPLICATE の場合は、複製されたレコードの ID もアクションとともに指定する必要があります。この値は、ユーザーによって要求され、original という名前の引数の値として、アクションの引数マップに置かれます。
start.addActionListener(new ActionListener(){
/**
* ビュー ダイアログのコンボ ボックスで現在選択されている
* アクションを使用してレコードの編集を開始します。
*/
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;
}
編集メソッドは、ユーザーがレコード ビューアで [編集] ボタンをクリックしたときに呼び出されます。編集メソッドには、編集されるレコードのプロキシおよび編集を実行するアクションのプロキシが渡されます。
ClearQuest® アクションの開始に使用されるアクション プロキシによってプロパティを定義する必要はありません。定義する必要があるのは、そのロケーションと引数マップ (必要な場合) のみです。アクションのロケーションを形成するには、アクションの <name>、アクションで使用するレコードの <record-type>、およびそのレコードがある場所の <database> および <db-set> がわかっている必要があります。
したがって、ロケーションは、cq.action:<record-type>/<name>@<db-set>/<database> になります。例えば、cq.action:Defect/Assign@7.0.0/SAMPL などです。
新しいプロキシを使用して、編集操作を開始するための CqRecord.doWriteProperties が呼び出されます。 書く必要があるプロパティは、レコードを編集するアクションのみです。doWriteProperties メソッドは、編集のために開かれたレコードのプロキシを戻し、そのプロキシに、RECORD_PROPERTIES PropertyRequest で要求されたプロパティが含まれます。 そのプロパティは、doWriteProperties が戻した後に、record.doReadProperties を (同じ PropertyRequest とともに) 使用して取得することもできますが、同じデータを取得するためにリポジトリへの往復が余分に必要になるため、効率が悪くなります。
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 は、showRecord に類似しています。主な違いは、これが TableModel.isCellEditable と 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 は、VALUE 列についてのみ、かつ、READ_ONLY 以外の REQUIREDNESS 値が指定されたフィールドに行が属する場合にかぎり TRUE を戻します。
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 は、表示に関連付けられた CqRecord プロキシに新規のフィールド値を設定します。最初に、CqFieldValue 構造が initialize() メソッドを使用して更新されます。このメソッドは、ほとんどのタイプのフィールドの文字列表記を受け入れ、その文字列をフィールドに適したデータ タイプに変換します。
CqFieldValue が一度更新されると、フィールドの PropertyName に関連付けられた CqRecord プロキシ内にそれが設定されます。 このステップは、レコードがコミットされたときに新規フィールド値がデータベースに書き込まれるようにするために必要です。このステップがないと、新規フィールド値は、ALL_FIELD_VALUES プロパティの一部である CqFieldValue オブジェクトにのみ残ります。ALL_FIELD_VALUES プロパティは書き込み可能ではないため、変更はデータベースに書き込まれません。修正された CqFieldValue をフィールドのプロキシ項目に直接コピーすることによって、そのフィールドの値は更新済みプロパティになり、更新された値が、プロキシ上で実行される次の do メソッドによってデータベースに書き込まれます。
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);
// 選択の変更を通知するよう求めます。
ListSelectionModel rowSM = table.getSelectionModel();
rowSM.addListSelectionListener(new ListSelectionListener() {
/**
* テーブルで選択したフィールドのタイプに基づいて
* ダイアログのボタンを使用可能に設定します。
*/
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);
}
}
}
});
[ビュー] ボタンは、ViewRecord.Viewer の [ビュー] ボタンと同じです。
buttons.add(show);
show.addActionListener(new ActionListener(){
/**
* テーブルで選択されたフィールドの値を使用してビュー メソッド
* を呼び出します。
*/
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"));
}
}
}
});
[デリバー] ボタンは、CqProvider.DELIVER オプションを指定して doWriteProperties() を呼び出して、修正されたレコードをデータベースにコミットします。デリバーが正常に行われた後、そのビューアは終了し、次に新規のビューアが元のプロキシに表示されます。view() メソッドがデータベースからプロパティを再読み取りするため、この新規ビューには更新された値が表示されます。
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);
}
}
});
[キャンセル] ボタンは、レコード プロキシの doRevert メソッドを使用して編集操作を中止します。 [デリバー] ボタンと同様、ビューアはクローズし、新規ビューアが元のプロキシ用にインスタンス化されます。
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);
}
}
});
[添付] および [削除] ボタンは、ViewRecord.Viewer のものと同じですが、デリバーの order 引数は DELIVER ではなく HOLD になります。これにより、添付ファイルの追加または削除のコミットは、[デリバー] ボタンによってレコード全体がコミットされるまで保留されます。
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;
}
この GUI は、フィールドが修正されるたびに、record.doWriteProperties の呼び出しを実行することができます。これによりユーザーにはすぐにフィードバックが提供されますが、クライアントとサーバーの間の通信プロトコルによっては非常に効率が悪くなることがあります。 また、この GUI には [更新] ボタンを用意することもできます。その場合、累積されているすべての更新が、実際にデリバーされることなくサーバーに書き込まれます。これにより、このスキーマには、値を調べてエラーのレポートを戻す機会が与えられます。これらの変更については読者の判断に任されます。どのアプローチを選択するかは、このアプリケーションの使用目的によって決まります。