001/*
002 * file CreateRecordCommand.java
003 *
004 * Licensed Materials - Property of IBM
005 * Restricted Materials of IBM - you are allowed to copy, modify and 
006 * redistribute this file as part of any program that interfaces with 
007 * IBM Rational CM API.
008 *
009 * com.ibm.rational.stp.client.samples.CreateRecordCommand
010 *
011 * (C) Copyright IBM Corporation 2004, 2008.  All Rights Reserved.
012 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
013 * disclosure restricted by GSA ADP  Schedule Contract with IBM Corp.
014 */
015package com.ibm.rational.stp.client.samples;
016
017import java.util.ArrayList;
018import java.util.Arrays;
019import java.util.ListIterator;
020import java.util.Map;
021import java.util.TreeMap;
022
023import javax.wvcm.PropertyNameList;
024import javax.wvcm.WvcmException;
025import javax.wvcm.PropertyNameList.PropertyName;
026import javax.wvcm.PropertyRequestItem.PropertyRequest;
027
028import com.ibm.rational.wvcm.stp.StpLocation;
029import com.ibm.rational.wvcm.stp.StpProperty;
030import com.ibm.rational.wvcm.stp.StpResource;
031import com.ibm.rational.wvcm.stp.StpLocation.Namespace;
032import com.ibm.rational.wvcm.stp.StpProvider.Domain;
033import com.ibm.rational.wvcm.stp.cq.CqFieldValue;
034import com.ibm.rational.wvcm.stp.cq.CqProvider;
035import com.ibm.rational.wvcm.stp.cq.CqRecord;
036import com.ibm.rational.wvcm.stp.cq.CqRecord.FieldName;
037
038/**
039 * A sample CM API application that demonstrates the use of the API to create
040 * or update a ClearQuest record in a command line context.
041 * <p>
042 * The first argument is either the type of the record to be created or the name
043 * of a record to be modified. Standard CM API selector syntax must be used.
044 * The target user database must be specified in the repo field, but the domain
045 * and the namespace fields may be omitted as they are understood to be <b>cq</b>
046 * and <b>record</b>, respectively.
047 * 
048 * The command-line options supported are
049 * <dl>
050 * <dt>-user <i>user-name</i>
051 * <dd>User logon name. Defaults to "admin"</dd>
052 * <dt>-pass <i>password</i>
053 * <dd>Password for the given user logon name. Defaults to ""</dd>
054 * <dt>-action <i>action name</i>
055 * <dd>The action to be used to update the record. Defaults to the default
056 * Modify-type action defined for the record type.</dd>
057 * <dt>[-set] <i>field-name</i> <i>field-value</i>
058 * <dd>Specify a new value for a field</dd>
059 * <dt>-setlist <i>field-name</i> <i>field-value</i>+
060 * <dd>Specify a list of new values for a field. The value list is terminated
061 * by the end of the command line or the next argument beginning with a hyphen</dd>
062 * <dt>-show
063 * <dd>Causes the current value of all fields to be displayed after the
064 * operation completes. (If a record is identified by the first parameter, no
065 * fields are specified, and no action is given, no operation is actually
066 * performed: just the fields of the specified record are displayed.)</dd>
067 * </dl>
068 */
069public class CreateRecordCommand
070{
071    /** Generally useful field meta-properties. * */
072    private static final PropertyNameList FIELD_METAPROPERTIES =
073        new PropertyNameList(CqFieldValue.VALUE,
074                             CqFieldValue.NAME,
075                             CqFieldValue.TYPE,
076                             CqFieldValue.REQUIREDNESS);
077
078    /** Property request for retrieving the user-friendly name of a resource */
079    public static final PropertyRequest RECORD_REQUEST =
080        new PropertyRequest(StpResource.USER_FRIENDLY_LOCATION,
081                            CqRecord.ALL_FIELD_VALUES.nest(StpProperty.VALUE
082                                .nest(FIELD_METAPROPERTIES)),
083                            CqRecord.STATE_NAME,
084                            CqRecord.DEFAULT_ACTION,
085                            CqRecord.LEGAL_ACTIONS,
086                            CqRecord.RECORD_CLASS);
087
088    /** The CqProvider instance used by the class */
089    private static CqProvider g_provider;
090
091    /**
092     * The database in which the record will be created determined from the
093     * command line inputs
094     */
095    private static String g_repo = "";
096
097    /**
098     * Creates an StpLocation given a partial specification of its selector
099     * 
100     * @param name A String containing at least the name field of the selector
101     * @param namespace The namespace required by context
102     * @return An StpLocation for the given location
103     * @throws WvcmException if the specified location is malformed.
104     */
105    static StpLocation normalize(Namespace namespace,
106                                 String name) throws WvcmException
107    {
108        StpLocation loc = g_provider.stpLocation(name);
109
110        if (loc.getDomain() == Domain.NONE)
111            loc = loc.recomposeWithDomain(Domain.CLEAR_QUEST);
112
113        if (loc.getNamespace() == Namespace.NONE) {
114            loc = loc.recomposeWithNamespace(namespace);
115        }
116
117        if (g_repo.equals(""))
118            g_repo = loc.getRepo();
119        else if (loc.getRepo().equals(""))
120            loc = loc.recomposeWithRepo(g_repo);
121
122        return loc;
123    }
124
125    /**
126     * Creates a new record using field values specified on the command line
127     * 
128     * @param args An array of String objects, each containing a command option
129     *            or command option argument as described in the class overview
130     *            comments.
131     * @throws Exception If things go wrong.
132     */
133    public static void main(String[] args) throws Exception
134    {
135        run(args, RECORD_REQUEST);
136        System.exit(0);
137    }
138
139    /**
140     * Creates a new record using field values specified on the command line
141     * 
142     * @param args An array of String objects, each containing a command option
143     *            or command option argument as described in the class overview
144     *            comments.
145     * @param wanted Target properties to be included in the returned proxy.
146     *            Must include USER_FRIENDLY_LOCATION
147     * @return A proxy for the created/modified record if the operation was
148     *         successful; otherwise <b>null</b>
149     * @throws Exception If things go wrong.
150     */
151    public static CqRecord run(String[] args,
152                               PropertyRequest wanted) throws Exception
153    {
154        // Establish a session to ClearQuest
155        g_provider = Utilities.getStaticProvider();
156
157        boolean doShow = false;
158        StpLocation actionLoc = null;
159        ListIterator<String> argl = Arrays.asList(args).listIterator(0);
160        StpLocation target = normalize(Namespace.RECORD, argl.next());
161        boolean doCreate = target.getNameSegmentCount() == 1;
162        CqRecord record =
163            g_provider.cqRecord(doCreate? (StpLocation) target.child("new")
164                                        : target);
165
166        // Read and process the remaining command line arguments
167        while (argl.hasNext()) {
168            String arg = argl.next();
169
170            if (arg.equals("-url"))
171                g_provider.setServerUrl(argl.next());
172            else if (arg.equals("-user"))
173                Utilities.g_user = argl.next();
174            else if (arg.equals("-password"))
175                Utilities.g_pass = argl.next();
176            else if (arg.equals("-action"))
177                actionLoc = normalize(Namespace.ACTION, argl.next());
178            else if (arg.equals("-show"))
179                doShow = true;
180            else if (arg.equals("-setlist")) {
181                arg = argl.next();
182
183                ArrayList<String> values = new ArrayList<String>();
184
185                while (argl.hasNext()) {
186                    String value = argl.next();
187
188                    if (value.startsWith("-")) {
189                        argl.previous();
190                        break;
191                    } else
192                        values.add(value);
193                }
194
195                record.setProperty(new FieldName<Object>(arg), values);
196            } else {
197                if (arg.equals("-set"))
198                    arg = argl.next();
199
200                record.setProperty(new FieldName<String>(arg), argl.next());
201            }
202        }
203
204        // Print the fields to be set
205        PropertyName<?>[] updated =
206            record.updatedPropertyNameList().getPropertyNames();
207
208        for (PropertyName<?> name : updated)
209            System.out.println(name.getName() + " => "
210                + record.getProperty(name));
211
212        // Read, create, or update record in database
213        try {
214            PropertyRequest rl = wanted;
215
216            if (doCreate) {
217                record = record.doCreateRecord(rl);
218                System.out.println("Created "
219                    + record.getUserFriendlyLocation());
220            } else if (actionLoc != null) {
221                // If record type was omitted from action location, insert it
222                // now.
223                // e.g.: input 'assign' became 'cq.action:assign@7.0.1/SAMPL';
224                // now make it 'cq.action:Defect/assign@7.0.1/SAMPL'
225                if (actionLoc.getNameSegmentCount() == 1)
226                    actionLoc =
227                        (StpLocation) actionLoc.parent().child(target.parent()
228                            .lastSegment()).child(actionLoc.lastSegment());
229
230                record =
231                    (CqRecord) record.setAction(g_provider.cqAction(actionLoc))
232                        .doWriteProperties(rl);
233                System.out.println(actionLoc.lastSegment() + "(ed)" + target);
234            } else {
235                // If no action was specified, use doReadProperties. Modified
236                // fields will be written if needed.
237                record = (CqRecord) record.doReadProperties(wanted);
238                System.out.println((updated.length > 0? "Updated" : "Found")
239                    + record.getUserFriendlyLocation());
240            }
241        } catch (WvcmException ex) {
242            System.out.println("Operation could not be completed: "
243                + ex.getLocalizedMessage());
244
245            // This will identify specific fields that were in error
246            for (Throwable nested : ex.getNestedExceptions())
247                System.out.println(nested.toString());
248
249            return null;
250        }
251
252        if (doShow) {
253            // Collect, sort, and print the field values
254            Map<String, Object> report = new TreeMap<String, Object>();
255            String pad = "";
256
257            for (CqFieldValue field : record.getAllFieldValues()) {
258                report.put(field.getName(), field.getValue());
259
260                while (field.getName().length() > pad.length())
261                    pad += " ";
262            }
263
264            for (String name : report.keySet()) {
265                System.out.println(pad.substring(name.length()) + name + " : "
266                    + showValue(pad + "   ", report.get(name)));
267            }
268        }
269
270        return record;
271    }
272
273    private static Object showValue(String pad,
274                                    Object value)
275    {
276        if (value != null) {
277            StringBuffer sb = new StringBuffer();
278            String prefix = "{";
279
280            if (value instanceof Iterable) {
281                for (Object item : (Iterable) value) {
282                    sb.append(prefix + item);
283                    prefix = "\n" + pad + " ";
284                }
285
286                sb.append("}");
287
288                return sb.toString();
289            } else if (value.getClass().isArray()) {
290                prefix = "[";
291
292                for (Object item : (Object[]) value) {
293                    sb.append(prefix + item);
294                    prefix = "\n" + pad + " ";
295                }
296
297                sb.append("]");
298
299                return sb.toString();
300            }
301        }
302
303        return value;
304    }
305}