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     */
015    package com.ibm.rational.stp.client.samples;
016    
017    import java.util.ArrayList;
018    import java.util.Arrays;
019    import java.util.ListIterator;
020    import java.util.Map;
021    import java.util.TreeMap;
022    
023    import javax.wvcm.PropertyNameList;
024    import javax.wvcm.WvcmException;
025    import javax.wvcm.PropertyNameList.PropertyName;
026    import javax.wvcm.PropertyRequestItem.PropertyRequest;
027    
028    import com.ibm.rational.wvcm.stp.StpLocation;
029    import com.ibm.rational.wvcm.stp.StpProperty;
030    import com.ibm.rational.wvcm.stp.StpResource;
031    import com.ibm.rational.wvcm.stp.StpLocation.Namespace;
032    import com.ibm.rational.wvcm.stp.StpProvider.Domain;
033    import com.ibm.rational.wvcm.stp.cq.CqFieldValue;
034    import com.ibm.rational.wvcm.stp.cq.CqProvider;
035    import com.ibm.rational.wvcm.stp.cq.CqRecord;
036    import 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     */
069    public 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    }