< Anterior | Siguiente >

Modificación de un registro

En esta lección aprenderá a modificar los valores de campo de un registro y añadir o eliminar archivos adjuntos.
En el ejemplo de código de esta lección el usuario puede actualizar el registro que está visualizando. En este ejemplo se amplía el ejemplo ViewRecord del módulo anterior ampliando la clase ViewRecord.Viewer para dar soporte a un recuadro combinado para seleccionar una acción y un botón Editar adicional para iniciar la acción seleccionada en el registro. El método editRecord de este Visor controla la visualización y actualización de un registro abierto para actualizarse. Estos nuevos componentes de la GUI se introducen en la pantalla de ViewRecord.Viewer utilizando el argumento future en showRecord.

Ahora el método view(CqRecord) lee la propiedad LEGAL_ACTIONS del registro además de la propiedad ALL_FIELD_VALUES. El valor de la propiedad LEGAL_ACTIONS es una lista de proxies Action (acción) correspondientes a las acciones que se pueden aplicar legalmente en el registro en su estado actual. Esta lista se utiliza para llenar un control de recuadro combinado, desde el cual el usuario puede seleccionar una acción antes de pulsar el botón de edición. Algunos tipos de acceso, como por ejemplo, SUBMIT y RECORD_SCRIPT_ALIAS no están soportados en este ejemplo y, por consiguiente, no se añaden al control del recuadro combinado aunque estén presentes en el conjunto de acciones legales.

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 también da soporte a un botón Conectar y un botón Desconectar para añadir y eliminar los archivos adjuntos de un campo de archivos adjuntos.

El botón Conectar presenta un diálogo de selección de archivos Swing estándar ante el usuario y le permite seleccionar el archivo que va a adjuntar. Para adjuntar el archivo al registro, se utiliza CqAttachment.doCreateAttachment(), al que se le pasa el nombre del archivo seleccionado por el usuario. Para poder invocar este método, debe crearse un proxy CqAttachment que direccione la ubicación adecuada. La carpeta en la que residirá el recurso de archivo adjunto es el valor del campo que está adjunto; es decir, el campo seleccionado actualmente en la vista de registro y por consiguiente, se obtiene del objeto de trama devuelto por ViewRecord.showRecord. A partir de la hora actual del día se genera un nombre exclusivo para el recurso de archivo adjunto. Este nombre es simplemente un marcador ya que doCreateAttachment() tiene la libertad de cambiar el nombre del recurso para satisfacer sus necesidades.

add.addActionListener(new ActionListener(){
            /**
             * Prompts the user for the name of a file to be attached to
             * the selected field. If so provided, the content of the 
             * file is read into the ClearQuest database as an attachment.
             */
            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);}
                }}});

El botón Desconectar utiliza ViewRecord.selectAttachment para obtener del usuario la identidad del archivo adjunto que se va a eliminar en forma de un proxy CqAttachment. A continuación, se invoca el método doUnbindAll de este proxy para eliminar el archivo adjunto de la base de datos.

        remove.addActionListener(new ActionListener(){
            /**
             * Allows the user to select an attachment associated with
             * the selected field. If the user selects such an attachment
             * it is deleted.
             */
            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);}
                }
            }
        );

El botón Editar capta el proxy CqAction seleccionado del recuadro combinado y transfiere dicho proxy así como el proxy del registro del visor actual al método de edición, que iniciará realmente la edición del registro. Si el CqAction.Type de la acción seleccionada es DUPLICATE, el id del registro duplicado debe suministrarse con la acción. Este valor se solicita al usuario y se coloca en la correlación de argumento de la Acción como el valor del argumento denominado original.

        start.addActionListener(new ActionListener(){
            /**
             * Starts editing of the record using the action currently
             * selected by the combo box on the view dialog.
             */
            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;
}

El método de edición se invoca cuando el usuario pulsa el botón Editar en el visor del registro. Se pasa como proxy para el registro que se va a editar y un proxy para la acción debajo de la cual se va a realizar la edición.

Recuerde que no es necesario que el proxy Acción defina ninguna propiedad para iniciar una acción ClearQuest; sólo deben definirse su ubicación y correlación de argumento (si es necesario). Para formar la ubicación de una acción, es necesario conocer el <nombre>, el <tipo-registro> del registro con el que se va a utilizar y la <base-datos> y <conjunto-bd> donde se encuentra el registro.

La ubicación es pues cq.action:<tipo-registro>/<nombre>@<conjunto-bd>/<base-datos>; un ejemplo de ello es cq.action:Defect/Assign@7.0.0/SAMPL

Con un proxy nuevo, se invoca CqRecord.doWriteProperties para iniciar la operación de edición. La única propiedad que se va a grabar es la acción debajo de la cual se va a editar el registro. El método doWriteProperties devuelve un proxy para el registro que se ha abierto para editar y el proxy que contendrá las propiedades solicitadas en RECORD_PROPERTIES PropertyRequest. Las propiedades también pueden obtenerse con record.doReadProperties (con el mismo PropertyRequest) después de que doWriteProperties vuelva, pero esto sería menos eficaz ya que requeriría otro viaje de ida y vuelta al depósito para los mismos datos.

        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 es similar a showRecord. La diferencia principal es que define TableModel.isCellEditable y 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 sólo devuelve el valor true para la columna VALUE y sólo si la fila pertenece a un campo cuya propiedad REQUIREDNESS no sea 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 establece el nuevo valor de campo en el proxy CqRecord asociado a la pantalla. En primer lugar, la estructura CqFieldValue se actualiza utilizando el método initialize(). Este método acepta una representación de serie para la mayoría de tipos de campos y convertirá la serie en el tipo de datos apropiados para el campo.

Cuando se haya actualizado CqFieldValue, se establece en el proxy CqRecord asociado a PropertyName para el campo. Este paso es necesario para que el nuevo valor de campo se grabe en la base de datos cuando se confirma el registro. Sin este paso, el nuevo valor de campo sólo permanecería en el objeto CqFieldValue que forma parte de la propiedad ALL_FIELD_VALUES. La propiedad ALL_FIELD_VALUES no se puede grabar, por lo que el cambio nunca se grabará en la base de datos. Al copiar el objeto CqFieldValue modificado directamente en la entrada de proxy del campo, dicho campo se convierte en una propiedad actualizada y el valor actualizado se grabará en la base de datos durante el siguiente método do que se ejecute en el 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);
    
    // Ask to be notified of selection changes.
    ListSelectionModel rowSM = table.getSelectionModel();
    rowSM.addListSelectionListener(new ListSelectionListener() {
        /**
         * Enables the buttons in the dialog based on the type of
         * field selected in the table.
         */
        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);
                    }
            }
        }
    });

El botón Ver es el mismo que el botón Ver de ViewRecord.Viewer.

    buttons.add(show);
    show.addActionListener(new ActionListener(){
            /**
             * Invokes a view method on the value of the field selected
             * in the table.
             */
            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"));
                    }
                }
            }
        });

El botón Entregar confirma el registro modificado en la base de datos llamando doWriteProperties() con la opción CqProvider.DELIVER. Cuando la entrega se ha realizado satisfactoriamente, el Visor desaparece y entonces aparece un Visor nuevo en el proxy original. Como el método view() vuelve a leer las propiedades de la base de datos; esta nueva vista visualizará los valores actualizados.

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

El botón Cancelar abandona la operación de edición utilizando el método doRevert del proxy de registro. En cuanto al botón Entregar, el Visor se cierra y se crea una instancia de un nuevo Visor para el 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);
                }
            }
        });

Los botones Conectar y Desconectar son los mismos que los de ViewRecord.Viewer, excepto que el argumento del pedido de entrega es HOLD en lugar de DELIVER. Esto hará que se postergue la confirmación de la adición o supresión del archivo adjunto hasta que el botón Entregar confirme todo el registro.

    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;
}
En este ejemplo, los valores del campo editado se guardan en el cliente hasta que el usuario seleccione el botón Entregar. Cada campo modificado por el usuario marca la propiedad correspondiente en el proxy del registro como actualizada. Estas propiedades actualizadas se graban en el servidor como el primer paso de la operación de entrega. Un problema que se plantea con este método es que los nuevos valores de campo no se comprueban hasta la entrega final, que podría resultar incorrecta si los valores no son apropiados para el esquema que se está utilizando.

La GUI podría realizar una llamada a record.doWriteProperties cada vez que se modifique un campo, otorgando así al usuario información de retorno inmediata, que también podría resultar muy ineficaz en el protocolo de comunicaciones entre el cliente y el servidor. La GUI también podría facilitar un botón Actualizar, que haría que todas las actualizaciones acumuladas se grabaran en el servidor sin entregarlas realmente. Esto brindaría al esquema una oportunidad para examinar los valores y volver a informar de los errores. Estas modificaciones se dejan para el lector y el método que se elija depende del uso previsto para esta aplicación.

Punto de comprobación de la lección

Ahora ha aprendido a utilizar la API CM de Rational para desarrollar acciones de aplicaciones cliente que realizan operaciones en los registros de una base de datos de usuario. La lección final de esta guía de aprendizaje demuestra cómo crear un registro nuevo.
En esta lección, ha aprendido lo siguiente:
  • Cómo utilizar la API CM de Rational para realizar operaciones en los recursos de una base de datos de usuarios desde una aplicación cliente.
  • Cómo crear operaciones en función del producto desde una aplicación cliente utilizando la API CM de Rational.
< Anterior | Siguiente >

Comentarios