它呼叫 ExecuteQuery.run,讓使用者能選取及執行查詢。在這個範例中,提供給 ExecuteQuerty.run 方法一個名稱為 ViewRecord.Viewer 的 ExeuteQuerty.Viewer 實例。這使得結果集畫面中出現開啟按鈕,按一下該按鈕即可呼叫 Viewer.view 方法。對於與結果集的所選取列相關聯的資源,會傳遞一個 Proxy 給 Viewer.view 方法(定義如下)。此 Proxy 是使用 CqRowData.getRecord() 方法取得的。
因此,本範例的內容包含在 Viewer.view 方法中:
static class Viewer implements ExecuteQuery.Viewer {
Viewer(CqProvider provider) { m_provider = provider; }
/**
* @see com.ibm.rational.stp.client.samples.ExecuteQuery.Viewer#view(com.ibm.rational.wvcm.stp.cq.CqRecord)
*/
public JFrame view(CqRecord record)
{
if (record != null) try {
record = (CqRecord)record.doReadProperties(RECORD_PROPERTIES);
return showRecord("View: ", record, null);
} catch (WvcmException ex){
ex.printStackTrace();
}
return null;
}
/**
* 在文字視窗顯示 Attachment 資源的內容。
*
* @param attachment 要顯示的附件之 Attachment
* Proxy。
*/
public void view(CqAttachment attachment)
{
if (attachment != null) try{
File file = File.createTempFile("attach", "tmp");
attachment.doReadContent(file.getAbsolutePath(), null);
BrowserDataModel.showFile(attachment.getDisplayName(), file);
} catch(Throwable ex) {
Utilities.exception(null, "View Attachment", ex);
}
}
/**
* 在表格中顯示記錄資源的 ALL_FIELD_VALUES 內容。
* 由 {@link #fieldMetaProperties} 陣列的內容決定表格的直欄。
* 由物件自己的 toString() 方法
* 實作大部分物件的顯示。
*
* @param title 包含表格的視窗之標題字串
* @param record 要顯示的記錄之 Record Proxy。必須
* 定義 ALL_FIELD_VALUES 內容,而且該內容中的 FieldValues
* 必須定義 {@link #fieldMetaProperties} 陣列中所列示的
* meta 內容。
* @param future 要與內容表一起顯示的
* 其他視窗元件。(由本範例的延伸規格使用)。
* @return 包含這個方法建立的 GUI 元件的
* RecordFrame 結構。
* @throws WvcmException
*/
RecordFrame showRecord(String title,
CqRecord record,
JComponent[] future) throws WvcmException
{
final StpProperty.List<CqFieldValue<?>> fields =
record.getAllFieldValues();
// 定義表格模型,其中每一列都是記錄資源的內容,
// 而且每一欄都是此內容的 meta 內容,
// 例如其名稱、類型及值;
TableModel dataModel = new AbstractTableModel() {
private static final long serialVersionUID = 1L;
public int getColumnCount() { return fieldMetaProperties.length; }
public int getRowCount() { return fields.size();}
public Object getValueAt(int row, int col)
{
try {
Object val = fields.get(row)
.getMetaProperty((MetaPropertyName<?>)
fieldMetaProperties[col].getRoot());
if (val instanceof CqRecord)
return ((CqRecord)val).getUserFriendlyLocation()
.getName();
else if (val instanceof CqAttachmentFolder)
return ((CqAttachmentFolder)val)
.getAttachmentList().size()
+ " attachments";
else
return val;
} catch(Throwable ex) {
if (ex instanceof StpException) {
return ((StpException)ex).getStpReasonCode();
} else {
String name = ex.getClass().getName();
return name.substring(name.lastIndexOf(".")+1);
}
}
}
public String getColumnName(int col)
{ return fieldMetaProperties[col].getRoot().getName(); }
};
// 定義顯示畫面佈置
final JTable table = new JTable(dataModel);
final JPanel panel = new JPanel(new BorderLayout());
final JPanel buttons = new JPanel(new FlowLayout());
final JButton button = new JButton("View");
final RecordFrame frame =
new RecordFrame(title + record.getUserFriendlyLocation().toString(),
table, fields);
// 新增按鈕來檢視所選取記錄或附件欄位
buttons.add(button, BorderLayout.SOUTH);
button.setEnabled(false);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent arg0)
{
int[] selected = table.getSelectedRows();
for (int i =0; i < selected.length; ++i) {
int row = selected[i];
if (isAttachmentList(fields, row)) {
view(selectAttachment(frame, fields, row, "View"));
} else {
view(getRecordReferencedAt(fields, row));
}
}
}
});
// 新增其他按鈕(供後續範例使用)
if (future != null)
for(int i = 0; i < future.length; ++i) buttons.add(future[i]);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// 要求收到選擇變更的通知,並唯有選取了
// 記錄值欄位或附件欄位時才啟用檢視按鈕
ListSelectionModel rowSM = table.getSelectionModel();
rowSM.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()){
int[] selected = table.getSelectedRows();
button.setEnabled(false);
for (int i=0; i <selected.length; ++i)
if (getRecordReferencedAt(fields, selected[i]) != null
|| isAttachmentList(fields, selected[i])) {
button.setEnabled(true);
break;
}
}
}
});
panel.add(new JScrollPane(table), BorderLayout.CENTER);
panel.add(buttons, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setContentPane(panel);
frame.setBounds(g_windowX += 10, g_windowY += 10, 600, 300);
frame.setVisible(true);
return frame;
}
protected CqProvider m_provider;
}
/**
* 向每一個記錄欄位值要求的內容,包括
* 附件的其他特定資訊
*/
static final PropertyRequest VALUE_PROPERTIES =
new PropertyRequest(StpResource.USER_FRIENDLY_LOCATION,
CqAttachmentFolder.ATTACHMENT_LIST
.nest(CqAttachment.DISPLAY_NAME,
CqAttachment.FILE_NAME,
CqAttachment.FILE_SIZE,
CqAttachment.DESCRIPTION));
/** 要求並顯示的欄位 meta 內容 */
static final NestedPropertyName[] fieldMetaProperties =
new PropertyRequest(CqFieldValue.NAME,
CqFieldValue.REQUIREDNESS,
CqFieldValue.TYPE,
CqFieldValue.VALUE.nest(VALUE_PROPERTIES)).toArray();
/**
* 從此檢視器要顯示的記錄中讀取資料時所使用的 PropertyRequest。
* 記下用來要求 ALL_FIELD_VALUES 清單中的欄位的 meta 內容的間接層次,
* 而不是 ALL_FIELD_VALUES 內容本身的那些 meta 內容。
*
*/
final static PropertyRequest RECORD_PROPERTIES =
new PropertyRequest(CqRecord.USER_FRIENDLY_LOCATION,
CqRecord.ALL_FIELD_VALUES
.nest(StpProperty.VALUE.nest(fieldMetaProperties)));
/**
* 檢查欄位的內容值,若該值參照某記錄,
* 則傳回所參照記錄的 Proxy。否則傳回空值。
* @param fields 要檢查的 Property.List。
* @param row 清單中要檢查的元素之索引。
* @return 如果欄位參照記錄,則傳回 Record Proxy;否則傳回空值
*/
static CqRecord getRecordReferencedAt(StpProperty.List<CqFieldValue<?>> fields,
int row)
{
try {
CqFieldValue field = fields.get(row);
if (field.getFieldType() == ValueType.RESOURCE
&& field.getValue() instanceof CqRecord)
return (CqRecord)field.getValue();
} catch (WvcmException ex) { ex.printStackTrace(); }
return null;
}
/**
* 指示的欄位是否為附件欄位。
* @param fields 要檢查的 Property.List。
* @param row 清單中要檢查的元素之索引。
* @return true 如果位於給定索引的欄位是附件欄位的話
*/
static boolean isAttachmentList(StpProperty.List<CqFieldValue<?>> fields,
int row)
{
if (row >= 0) try {
CqFieldValue field = fields.get(row);
return field.getFieldType() == ValueType.ATTACHMENT_LIST;
} catch (WvcmException ex) { ex.printStackTrace(); }
return false;
}
/**
* 向使用者呈現與記錄的指定欄位相關聯的附件清單,
* 讓使用者能夠選取其中之一。
*
* @param frame 這個方法產生的對話框之母框。
* @param fields 要檢查的 Property.List。
* @param row 清單中要檢查的元素之索引。
* @param op 識別會對所選取的附件
* 執行之作業的字串。
* @return 所選取附件的 Attachment Proxy;如果使用者決定不做任何選擇,
* 則傳回空值。
*/
static CqAttachment
selectAttachment(Component frame,
StpProperty.List<CqFieldValue<?>> fields,
int row,
String op)
{
CqFieldValue field = fields.get(row);
try {
CqAttachmentFolder folder = (CqAttachmentFolder)field.getValue();
ResourceList<CqAttachment> attachments = setUserFriendlyLocation
(folder.doReadProperties(ATTACHMENT_PROPERTIES)
.getProperty(CqAttachmentFolder.ATTACHMENT_LIST));
if (attachments.size() > 0) {
CqAttachment attachment =
(CqAttachment) JOptionPane
.showInputDialog(frame,
"Choose an Attachment to " + op,
op + " Attachment",
JOptionPane.INFORMATION_MESSAGE,
null,
attachments.toArray(),
attachments.get(0));
return attachment;
}
} catch(Throwable t) { Utilities.exception(frame, op + " Attachment", t);}
return null;
}
/**
* 要在 {@link #selectAttachment} 所產生的附件選擇清單中顯示的
* 附件內容。
*/
final static PropertyRequest ATTACHMENT_PROPERTIES =
new PropertyRequest(CqAttachmentFolder.ATTACHMENT_LIST
.nest(CqAttachment.DISPLAY_NAME,
CqAttachment.FILE_NAME,
CqAttachment.FILE_SIZE,
CqAttachment.DESCRIPTION,
CqAttachment.USER_FRIENDLY_LOCATION));
/**
* ViewRecord 範例的主程式。實例化 Provider,
* 然後呼叫 ExecuteQuery 範例,傳入會顯示 ClearQuest 記錄之
* 欄位的某一版 Viewer。
* @param args 不使用。
*/
public static void main(String[] args)
{
try {
CqProvider provider = Utilities.getProvider().cqProvider();
ExecuteQuery.run("View Record", provider, new Viewer(provider));
} catch(Throwable ex) {
Utilities.exception(null, "View Record", ex);
System.exit(0);
}
}
/**
* 記錄欄位顯示的 JFrame 延伸規格,
* 向用戶端顯示頁框的 JTable 元件
* 以及表格中所顯示的欄位清單。
*/
static class RecordFrame extends JFrame
{
RecordFrame(String title,
JTable table,
StpProperty.List fields)
{
super(title);
m_table = table;
m_fields = fields;
}
JTable m_table;
StpProperty.List m_fields;
private static final long serialVersionUID = 1L;
}
/** 要顯示的下一個視窗之 X 偏移 */
private static int g_windowX = 200;
/** 要顯示的下一個視窗之 Y 偏移 */
private static int g_windowY = 200;
在這個範例中,ViewRecord.Viewer 不僅用來顯示查詢傳回的記錄,亦可檢視記錄的選擇欄位參照的記錄,以及檢視附加至記錄欄位的檔案。使用者可使用此功能來瀏覽各個記錄的參照。
ViewRecord.view(CqRecord) 會從資料庫中讀取該記錄的所有欄位,及檢視器使用的每個欄位的 meta 內容,並將移入的 Proxy 傳給 showRecord 方法。檢視器使用 ClearQuest® 記錄的 ALL_FIELD_VALUES 內容取得記錄中所有欄位的清單。就各個欄位而言,皆需要 NAME、REQUIREDNESS、TYPE 及 VALUE meta 內容。請注意,這些 meta 內容要求在另一個 VALUE meta 內容要求之下形成巢狀,因此是為 ALL_FIELD_VALUES 內容的值取得這些 meta 內容,而不是為 ALL_FIELD_VALUES 內容本身取得。(請參閱 RECORD_PROPERTIES、VALUE_PROPERTIES 及 fieldMetaProperties 的宣告。)
如果欄位是附件欄位,則也需要該值的 ATTACHMENT_LIST 內容。(如果欄位不是附件欄位,這個內容要求會失敗,但因為唯有欄位是附件時才會存取此內容,所以這種失敗不會導致異常。)
ViewRecord.showRecord 使用與 ExecuteQuery.showResults 相同的 Swing GUI 元件和結構,僅表格的列和欄內容有差異。在此情況下,每一列是記錄的一個欄位,並以 CqFieldValue 物件來表示。每一欄是欄位的 meta 內容。通用 StpProperty.getMetaProperty 介面是用來從每個欄位的 CqFieldValue/StpProperty 結構提取每一個 meta 內容值。但有兩種例外情形,要在記錄視圖中產生 meta 內容的影像,需仰賴每一個 meta 內容值的 toString() 方法。以記錄資源而言,僅顯示 USER_FRIENDLY_LOCATION 內容的名稱欄位,以減少輸出的雜亂情況。以附件欄位而言,僅顯示附件數目,但不顯示每一個附件名稱。
欄位類型 RESOURCE_LIST、JOURNAL、STRING_LIST 及 STRING 的顯示也要特別留意。 這留待讀者自行練習。
選取附件欄位並按一下檢視按鈕後,便會呼叫 selectAttachment,且其結果會傳給 ViewRecord.view(CqAttachment)。 selectAttachment 方法再次使用 JOptionPane.showInputDialog,向使用者呈現與所選取欄位相關聯的附件清單。附件欄位的值就是附件資料夾資源。與此欄位相關聯的附件是該附件資料夾的連結成員。
ViewRecord.view(CqAttachment) 使用 CqAttachment.doReadContent,將資料庫中的附件檔讀入暫存檔,然後呼叫公用程式方法(全部都是 Swing 程式碼),對使用者顯示該檔案。
ViewRecord.Viewer 也應支援在個別視窗中顯示 RESOURCE_LIST 值。但同樣地,這也留待讀者自行練習。