< Anterior | Próximo >

Modificando um Registro

Nesta lição, você aprende como modificar os valores de campo de um registro e incluir ou remover anexos.
O exemplo de código desta lição permite que um usuário atualize o registro que ele está visualizando. Este exemplo estende o exemplo do ViewRecord do módulo anterior estendendo a classe do ViewRecord.Viewer para suportar uma caixa de combinação para selecionar uma ação e um botão adicional Editar para iniciar a ação selecionada no registro. O método editRecord deste Visualizador orquestra a visualização e atualização de um registro aberto para atualização. Estes novos componentes da GUI são apresentados na exibição do ViewRecord.Viewer quando o argumento futuro para showRecord.

O método de visualização(CqRecord) agora lê a propriedade LEGAL_ACTIONS do registro, além da propriedade ALL_FIELD_VALUES. O valor da propriedade LEGAL_ACTIONS é uma lista de proxies de Ação para as ações que podem ser legalmente aplicadas ao registro em seu estado atual. Esta lista é usada para preencher um controle de caixa de combinação, a partir da qual o usuário pode selecionar uma ação antes de clicar no botão editar. Alguns tipos de ações, como SUBMIT e RECORD_SCRIPT_ALIAS não são suportadas por este exemplo e não são, portanto, incluídas no controle da caixa de combinação mesmo se presentes no conjunto de ações legais.

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});

O EditView.Viewer também suporta um botão Anexar e Separar para incluir e remover os arquivos anexados de um campo de anexo.

O botão Anexar apresenta um diálogo de seleção de arquivo Swing padrão para o usuário e permite que ele selecione o arquivo a ser anexado. Para anexar o arquivo ao registro, CqAttachment.doCreateAttachment() é usado, passando a ele o nome do arquivo selecionado pelo usuário. Antes que este método possa ser invocado, um proxy CqAttachment que remeta ao local adequado deve ser construído. A pasta na qual o recurso de anexo irá residir é o valor do campo ao qual ele está anexado - este é o campo atualmente selecionado na visualização do registro e, então, é obtido a partir do objeto do quadro retornado pelo ViewRecord.showRecord. Um nome exclusivo para o recurso do anexo é gerado a partir da hora atual do dia. Este nome é meramente um marcador já que o doCreateAttachment() é livre para alterar o nome do recurso para adequar-se a suas necessidades.

add.addActionListener(new ActionListener(){
            /**
             * Solicita ao usuário o nome de um arquivo a ser anexado ao
             * campo selecionado. Se fornecido, o conteúdo do 
             * arquivo é lido no banco de dados do ClearQuest como um anexo.
             */
            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);}
                }}});

O botão Separar usa o ViewRecord.selectAttachment para obter do usuário a identidade do anexo a ser removido em forma de um proxy CqAttachment. O método doUnbindAll deste proxy é, então, invocado para remover o anexo do banco de dados.

        remove.addActionListener(new ActionListener(){
            /**
             * Permite que o usuário selecione um anexo associado ao
             * campo selecionado. Se o usuário selecionar tal anexo
             * ele é excluído.
             */
            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);}
                }
            }
        );

O botão Editar busca o proxy CqAction selecionado a partir da caixa de combinação e passa esse proxy e o proxy de registro para o visualizador atual para o método de edição, o qual irá realmente iniciar a edição do registro. Se o CqAction.Type da ação selecionada for DUPLICADO, então o id do registro duplicado deve ser fornecido com a ação. Este valor é solicitado pelo usuário e é colocado no mapa de argumentos da Ação como o valor do argumento denominado original.

        start.addActionListener(new ActionListener(){
            /**
             * Começa a edição do registro usando a ação atualmente
             * selecionada pela caixa de combinação no diálogo de visualização.
             */
            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;
}

O método de edição é invocado quando o usuário clica no botão Editar no visualizador de registros. É passado um proxy para o registro a ser editado e um proxy para a ação sob a qual a edição deve ser executada.

Observe que nenhuma propriedade precisa ser definida pelo proxy Ação usado para iniciar uma açãoClearQuest - apenas seu local e seu mapa de argumento (se necessário) deve ser definido. Para formar o local para uma ação, é necessário conhecer seu <nome>, o <record-type> do registro com que vai ser usado e o <banco de dados> e <db-set> onde o registro está localizado.

O local é, então, cq.action:<record-type>/<name>@<db-set>/<database>; um exemplo é, cq.action:Defect/Assign@7.0.0/SAMPL

Usando um novo proxy, CqRecord.doWriteProperties é invocado a iniciar a operação de edição. A única propriedade que deve ser gravada é a ação sob a qual o registro deve ser editado. O método doWriteProperties retorna um proxy para o registro que foi aberto para editar e esse proxy irá conter as propriedades solicitadas no RECORD_PROPERTIES PropertyRequest. As propriedades também poderiam ser obtidas usando record.doReadProperties (com o mesmo PropertyRequest) depois que o doWriteProperties retorna, mas isso seria menos eficiente como iria requerer outra roundtrip para o repositório para os mesmos dados.

        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 é semelhante ao showRecord. A diferença primária é que ele define TableModel.isCellEditable and 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 retorna como true somente para a coluna VALUE e somente se as linhas pertencem a um campo do qual a REQUIREDNESS não é 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 define o novo valor do campo no proxy CqRecord associado com a exibição. Primeiro, a estrutura CqFieldValue é atualizada usando seu método initialize(). Este método aceita uma representação em cadeia para a maioria dos tipos de campos e irá converter a cadeia no tipo de dados apropriado para o campo.

Quando o CqFieldValue tiver sido atualizado, ele será definido no proxy CqRecord associado com o PropertyName para o campo. Esta etapa é necessária para que o valor do novo campo seja gravado no banco de dados quando o registro for consolidado. Sem esta etapa, o novo valor do campo permaneceria somente no objeto CqFieldValue que é parte da propriedade ALL_FIELD_VALUES. A propriedade ALL_FIELD_VALUES não é gravável, assim, a mudança nunca seria gravada no banco de dados. Copiando o CqFieldValue modificado diretamente para a entrada do proxy para o campo, esse campo se torna uma propriedade atualizada e o valor atualizado será gravado no banco de dados pelo próximo métododo, que é executado no 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);
    
    // Pedir para ser notificado das mudanças de seleção.
    ListSelectionModel rowSM = table.getSelectionModel();
    rowSM.addListSelectionListener(new ListSelectionListener() {
        /**
         * Ativa os botões no diálogo com base no tipo de
         * campo seleciondo na tabela.
         */
        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);
                    }
            }
        }
    });

O botão Visualizar é igual ao botão Visualizar em um ViewRecord.Viewer.

    buttons.add(show);
    show.addActionListener(new ActionListener(){
            /**
             * Invoca um método de visualização no valor do campo selecionado
             * na tabela.
             */
            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(campos, linha);
                    
                    if (record != null) {
                        view(record);
                    } else if (ViewRecord.isAttachmentList(fields, row)) {
                        view(ViewRecord.selectAttachment(quadro, campos, linha, "Visualizar"));
                    }
                }
            }
        });

O botão Entregar consolida o registro modificado no banco de dados chamando o doWriteProperties() com a opção CqProvider.DELIVER. Depois que a entrega ocorre, o Visualizador é minimizado e, em seguida, um novo Visualizador aparece no proxy original. Como o método view() relê as propriedades a partir do banco de dados, esta nova visualização irá exibir os valores atualizados.

    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);
                }
            }
        });

O botão Cancelar abandona a operação de edição usando o método doRevert do proxy do registro. Como com o botão Entregar, o Visualizador é fechado e um novo Visualizador é instanciado para o proxy original.

    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);
                }
            }
        });

Os botões Anexar e Separar são iguais àqueles para o ViewRecord.Viewer, exceto que o argumento do pedido de entrega é SUSPENDER ao invés de ENTREGAR. Isso irá consolidar a inclusão ou exclusão do anexo até que o registro inteiro seja consolidado pelo botão Entregar.

    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;
}
Neste exemplo, os valores do campo editado são mantidos no cliente até que o usuário selecione o botão Entregar. Cada campo modificado pelo usuário marca a propriedade correspondente no proxy do registro como tendo sido atualizado. Estas propriedades atualizadas são gravadas no servidor como a primeira etapa da operação de entrega. Um problema com esta abordagem é que os novos valores do campo não são verificados até a entrega final, que poderia falhar se os valores não fossem apropriados para o esquema em uso.

A GUI poderia executar uma chamada para o record.doWriteProperties cada vez que um campo é modificado, o que poderia dar o feedback imediato ao usuário, mas também poderia ser terrivelmente ineficiente dependendo do protocolo de comunicação entre o cliente e o servidor. A GUI também poderia fornecer um botão Atualizar, que faria com que todas as atualizações acumuladas fossem gravadas no servidor, sem realmente entregá-las. Isso daria ao esquema uma oportunidade de examinar os valores e relatar erros de retorno. Estas modificações são deixadas para o leitor e a abordagem tomada dependeria do uso pretendido deste aplicativo.

Ponto de verificação da lição

Agora você aprendeu como usar o ClearQuest CM API para desenvolver ações do aplicativo cliente que executam operações sobre registros em um banco de dados do usuário. A lição final deste tutorial demonstra como criar um novo registro.
Nesta lição, você aprendeu o seguinte:
  • Sobre como usar o ClearQuest CM API para executar operações sobre recursos em um banco de dados do usuário a partir de um aplicativo cliente.
  • Como criar operações específicas do produto a partir de um aplicativo cliente usando o ClearQuest CM API.
< Anterior | Próximo >

Feedback