< 이전 | 다음 >

레코드 수정

이 단원에서는 레코드 필드 값을 수정하고 첨부를 추가 또는 제거하는 방법을 학습합니다.
이 단원의 코드 예제로 사용자는 보고 있는 레코드를 업데이트할 수 있습니다. 이 예제는 조치 및 추가 편집 단추를 선택하고 레코드의 선택된 조치를 시작하는 콤보 상자를 지원하도록 ViewRecord.Viewer 클래스를 확장하여 이전 모듈의 ViewRecord 예제를 확장합니다. 이 뷰어의 editRecord 메소드는 업데이트를 위해 열어둔 레코드의 보기 및 업데이트를 조정합니다. 이러한 새 GUI 컴포넌트가 showRecord에 future 인수를 사용하는 ViewRecord.Viewer 표시에 도입되었습니다.

이제 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 프록시 형식으로 제거할 첨부 ID를 사용자로부터 가져옵니다. 그런 다음 이 프록시의 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이라는 인수 값으로 Action의 인수 맵에 넣습니다.

        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® 조치를 시작하는데 사용되는 Action 프록시는 아무 특성도 정의할 필요가 없음에 유의하십시오. 오직 해당 위치 및 인수 맵(필요한 경우)만 정의해야 합니다. 조치에 대한 위치를 구성하려면 해당 <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 열의 경우에 대해서만, 그리고 REQUIREDNESS가 READ_ONLY가 아닌 필드에 행이 속해 있는 경우에만 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"));
                    }
                }
            }
        });

전달 단추는 doWriteProperties()를 CqProvider.DELIVER 옵션과 함께 호출하여 수정된 레코드를 데이터베이스에 확약합니다. 전달이 성공적인 경우 해당 뷰어는 사라지고 새 뷰어가 원래 프록시에 표시됩니다. 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);
                }
            }
        });

첨부분리 단추는 전달 순서 인수가 DELIVER가 아닌 HOLD라는 점만 제외하고 ViewRecord.Viewer에서와 동일합니다. 이것은 전체 레코드가 전달 단추에 의해 확약될 때까지 첨부의 추가 또는 삭제 확약을 지연합니다.

    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의 업데이트 단추를 선택하면 누적된 모든 업데이트를 전달하지 않은 상태로 서버에 작성하게 됩니다. 이를 수행함으로써 스키마는 값을 검토하고 오류를 다시 보고할 수 있습니다. 이러한 수정은 사용자가 계획한 이 애플리케이션의 용도에 따라 사용될 접근법이 달라집니다.

단원 체크포인트

이제 사용자 데이터베이스의 레코드에서 오퍼레이션을 수행하는 클라이언트 애플리케이션 조치 개발을 위해 Rational CM API를 사용하는 법을 학습했습니다. 이 학습서의 최종 단원은 새 레코드를 작성하는 방법을 보여주는 것입니다.
이 단원에서는 다음 사항을 학습했습니다.
  • 클라이언트 애플리케이션에서 사용자 데이터베이스의 자원에 오퍼레이션을 수행하기 위해 RationalCM API 사용.
  • Rational CM API를 사용하여 클라이언트 애플리케이션에서 제품 특정 오퍼레이션을 작성하는 법.

피드백
< 이전 | 다음 >