1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2010, 2011 Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 
  28 package com.sun.javatest.exec;
  29 
  30 import com.sun.javatest.InterviewParameters;
  31 import com.sun.javatest.Parameters;
  32 import com.sun.javatest.TestFilter;
  33 import com.sun.javatest.TestSuite;
  34 import com.sun.javatest.WorkDirectory;
  35 import com.sun.javatest.util.Debug;
  36 import com.sun.javatest.util.I18NResourceBundle;
  37 import java.io.File;
  38 import java.io.FileNotFoundException;
  39 import java.util.ArrayList;
  40 import java.util.Collections;
  41 import java.util.Comparator;
  42 import java.util.List;
  43 import java.util.Map;
  44 import java.util.logging.Logger;
  45 
  46 /**
  47  * The very classic implementation of Session that encapsulates the WorkDirectory
  48  * instance and InterviewParameters instance.
  49  *
  50  * @author Dmitry Fazunenko
  51  */
  52 public class BasicSession implements SessionExt {
  53 
  54     /**
  55      * Instance of configuration
  56      */
  57     private final InterviewParameters config;
  58 
  59     /**
  60      * Instance of workdir
  61      */
  62     private WorkDirectory wd;
  63 
  64     /**
  65      * List of registered observers
  66      */
  67     protected final List<Observer> observers = new ArrayList<Observer>();
  68 
  69     /**
  70      * List of available filters
  71      */
  72     protected final List<String> filterNames = new ArrayList<String>();
  73 
  74     /**
  75      * List of observable properties
  76      */
  77     protected final List<String> props = new ArrayList<String>();
  78 
  79     static final String EL_FILTER = "ExcludeList";
  80     static final String PRIOR_FILTER = "PriorStatus";
  81     static final String KWD_FILTER = "Keywords";
  82     static final String RELEVANT_FILTER = "Relevant";
  83 
  84     public static final String CONFIG_NAME_PROP = "Configuration";
  85     public static final String WD_PROP = "WorkDir";
  86 
  87     private boolean isSorted = false;
  88 
  89     private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(BasicSession.class);
  90 
  91     /**
  92      * Class of update to configuration
  93      */
  94     public static class U_NewConfig implements Update {
  95         public final InterviewParameters ip;
  96         public U_NewConfig(InterviewParameters ip) {
  97             this.ip = ip;
  98         }
  99     }
 100 
 101     /**
 102      * Class of update to WorkDirectory
 103      */
 104     static class U_NewWD implements Update {
 105         public final WorkDirectory wd;
 106         public U_NewWD(WorkDirectory wd) {
 107             this.wd = wd;
 108         }
 109     }
 110 
 111 
 112     /**
 113      * Event which is delivered when WorkDircotry has been set.
 114      */
 115     public static class E_NewWD implements Event {
 116         public final WorkDirectory wd;
 117         public final boolean doRestoreConfig; // for optimization
 118         E_NewWD(WorkDirectory wd, boolean doRestoreConfig) {
 119             this.wd = wd;
 120             this.doRestoreConfig = doRestoreConfig;
 121         }
 122     }
 123 
 124     /**
 125      * Event which is delivered when current configuration has been modified.
 126      */
 127     public static class E_NewConfig implements Event {
 128         public final InterviewParameters ip;
 129         public E_NewConfig(InterviewParameters ip) {
 130             this.ip = ip;
 131         }
 132     }
 133 
 134     /**
 135      * Extension to the Observer interface for those classes which
 136      * are sensitive to the order of notifying. If an observer wants
 137      * to be notified in the very first turn, it should implements OrderedObserver
 138      * interface, not just Observer and implement the order() method to return
 139      * Integer.MIN_VALUE. To be notified last, the order() method should return
 140      * Integer.MAX_VALUE. The order of regular observers is zero.
 141      */
 142     public static interface OrderedObserver extends Observer {
 143         /**
 144          * Returns number from Integer.MIN_VALUE to Integer.MAX_VALUE
 145          * to be sorted by when notifying.
 146          */
 147         public int order();
 148     }
 149 
 150 
 151     /**
 152      * Creates empty session for the passed test suite.
 153      * @param ts
 154      * @throws com.sun.javatest.exec.Session.Fault
 155      */
 156     public BasicSession(TestSuite ts) throws Fault {
 157         initFilterList();
 158         initPropertyList();
 159         try {
 160             config = ts.createInterview();
 161         } catch (Exception e) {
 162             throw new Fault(e);
 163         }
 164     }
 165 
 166    /**
 167     * Applies the update. Ignores updates of unknown type. Subclasses need
 168     * override this method to support more update types.
 169     * @param u
 170     * @throws com.sun.javatest.exec.Session.Fault
 171     */
 172    public void update(Update u) throws Fault {
 173         // here to preserve 4.4.0 behavior (true)
 174         update(u, true);
 175     }
 176 
 177 
 178       /**
 179     * Applies the update. Ignores updates of unknown type. Subclasses need
 180     * override this method to support more update types.
 181     * @param u
 182     * @throws com.sun.javatest.exec.Session.Fault
 183     * @since 4.4.1
 184     */
 185    public void update(Update u, boolean updateConfig) throws Fault {
 186         if (u instanceof U_NewWD) {
 187             updateWorkDir(((U_NewWD)u).wd, updateConfig);
 188         } else if (u instanceof U_NewConfig) {
 189             updateNewConfig(((U_NewConfig)u).ip);
 190         }
 191     }
 192 
 193     public void addObserver(Observer obs) {
 194         if (obs != null && !observers.contains(obs)) {
 195             observers.add(obs);
 196             isSorted = false;
 197         }
 198     }
 199 
 200     public void removeObserver(Observer obs) {
 201         if (obs != null && observers.contains(obs)) {
 202             observers.remove(obs);
 203         }
 204     }
 205 
 206     /**
 207      * Delivers events to the all registered observers
 208      * @param evn - event to be sent out.
 209      */
 210     public void notifyObservers(Event evn) {
 211         if (!isSorted) {
 212             sortObservers();
 213         }
 214         for (Observer obs: observers) {
 215             queue.add(new Pair(obs, evn));
 216         }
 217         notifyQueue();
 218     }
 219 
 220     private final ArrayList<Pair> queue  = new ArrayList<Pair>();
 221     private boolean isNotifying = false;
 222     private static class Pair {
 223         final Observer obs;
 224         final Event evn;
 225         Pair(Observer obs, Event evn) {
 226             this.obs = obs;
 227             this.evn = evn;
 228         }
 229     }
 230     private void notifyQueue() {
 231         if (isNotifying) {
 232             return; // already working...
 233         }
 234         isNotifying = true;
 235         boolean cont = queue.size() > 0;
 236         while (cont) {
 237            Pair pair = queue.remove(0);
 238            pair.obs.updated(pair.evn); // this call may cause a new
 239                                        // pair to be add to the queue
 240            cont = queue.size() > 0;
 241         }
 242         isNotifying = false;
 243     }
 244 
 245     /**
 246      * Sorts observers by their order.
 247      */
 248     private void sortObservers() {
 249         Collections.sort(observers, new Comparator<Observer>() {
 250             public int compare(Observer o1, Observer o2) {
 251                 long order1 = 0;
 252                 if (o1 instanceof OrderedObserver) {
 253                     order1 = ((OrderedObserver)o1).order();
 254                 }
 255                 long order2 = 0;
 256                 if (o2 instanceof OrderedObserver) {
 257                     order2 = ((OrderedObserver)o2).order();
 258                 }
 259                 return (int)(order1 - order2); // long is used to avoid overflow
 260             }
 261 
 262         });
 263         isSorted = true;
 264     }
 265 
 266     public TestFilter getTestFilter(String name) {
 267         if (config == null) {
 268             throw new IllegalStateException(i18n.getString("bc.configNotReady.err"));
 269         }
 270         TestFilter tf;
 271         if (filterNames.contains(name)) {
 272             tf = findFilter(name);
 273             if (tf != null) {
 274                 return tf;
 275             }
 276         }
 277         throw new IllegalArgumentException(i18n.getString("bc.unknownFilter.err", name));
 278     }
 279 
 280     /**
 281      * Supposed to be overridden when extra filters added
 282      * @param name
 283      * @return found filter or null, if not found.
 284      */
 285     protected TestFilter findFilter(String name) {
 286         if (EL_FILTER.equals(name)) {
 287             return config.getExcludeListFilter();
 288         } else if (KWD_FILTER.equals(name)) {
 289             return config.getKeywordsFilter();
 290         } else if (PRIOR_FILTER.equals(name)) {
 291             return config.getPriorStatusFilter();
 292         } else if (RELEVANT_FILTER.equals(name)) {
 293             return config.getRelevantTestFilter();
 294         }
 295         return null;
 296     }
 297 
 298     public List<String> getTestFilterNames() {
 299         return filterNames;
 300     }
 301 
 302     public void save(Map<String, String> map) {
 303         if (wd != null)
 304             map.put("workDir", wd.getPath());
 305         // save name of interview file
 306         if (config != null && config.getFile() != null)
 307             map.put("config", config.getFile().getPath());
 308     }
 309 
 310     public void restore(Map map) throws Fault {
 311         if (map == null)
 312             return;
 313 
 314         String wdPath = (String)map.get("workDir");
 315         if (wdPath == null) {
 316             return;
 317         }
 318         try {
 319             WorkDirectory workDir = WorkDirectory.open(new File(wdPath), config.getTestSuite());
 320             updateWorkDir(workDir, false);
 321             //this.wd = workDir;
 322             //applyWorkDir(wd);
 323         } catch (FileNotFoundException e) {
 324             // It's ok - saved WD could be removed or moved
 325             return;
 326         } catch (Exception e) {
 327             throw new Fault(e);
 328         }
 329 
 330         String ipPath = (String)map.get("config");
 331         if (ipPath == null) {
 332             return;
 333         }
 334         try {
 335             loadInterviewFromFile(wd, new File(ipPath));
 336         } catch (Exception e) {
 337             throw new Fault(e);
 338         }
 339     }
 340 
 341     /**
 342      * Loads interview from file.
 343      * @param wd
 344      * @param cfgFile
 345      * @throws com.sun.javatest.exec.Session.Fault
 346      */
 347     public void loadInterviewFromFile(WorkDirectory wd, File cfgFile) throws Fault {
 348         try {
 349             final long start = System.currentTimeMillis();
 350 
 351             config.load(cfgFile);
 352             logLoadTime("exec.log.iload", System.currentTimeMillis()-start,
 353                 wd, cfgFile.getAbsolutePath());
 354             config.setWorkDirectory(wd);
 355             notifyObservers(new E_NewConfig(config));
 356         } catch (Exception e) {
 357             throw new Fault(e);
 358         }
 359     }
 360 
 361     public void dispose() {
 362         config.dispose();
 363     }
 364     public List<String> getPropertyNames() {
 365         return props;
 366     }
 367 
 368     public String getValue(String name) {
 369         if (props.contains(name)) {
 370             if (WD_PROP.equals(name)) {
 371                 return wd == null ? null : wd.getPath();
 372             } else if (CONFIG_NAME_PROP.equals(name)) {
 373                 if (config == null) {
 374                     return null;
 375                 }
 376                 File f = config.getFile();
 377                 return f == null ?  null : f.getPath();
 378             }
 379         }
 380         throw new IllegalArgumentException(i18n.getString("bc.unknownProperty.err", name));
 381     }
 382 
 383     /**
 384      * Work directory assigned to the session.
 385      * @return The current wd set.
 386      */
 387     public WorkDirectory getWorkDirectory() {
 388         return wd;
 389     }
 390 
 391     public InterviewParameters getInterviewParameters() {
 392         return config;
 393     }
 394 
 395     public Parameters getParameters() {
 396         return getInterviewParameters();
 397     }
 398 
 399     public boolean isReady() {
 400         return config != null && config.isFinishable() && config.getFile() != null;
 401     }
 402 
 403     /**
 404      * Creates list of supported filters: ExcludeList, PriorStatus, Keyword,
 405      * Relevant.
 406      */
 407     protected void initFilterList() {
 408         filterNames.add(EL_FILTER);
 409         filterNames.add(PRIOR_FILTER);
 410         filterNames.add(KWD_FILTER);
 411         filterNames.add(RELEVANT_FILTER);
 412     }
 413 
 414     /**
 415      * Creates list of two session properties: WorkDirectory and Configuration.
 416      */
 417     protected void initPropertyList() {
 418         props.add(WD_PROP);
 419         props.add(CONFIG_NAME_PROP);
 420     }
 421 
 422     /**
 423      * Sets the work dir to the new specified value, inovkes applyWorkDir()
 424      * method, notifies observers of the work dir change.
 425      * <p>
 426      * It's not recommended to override this method.
 427      * @param wd - instance of WorkDirectory
 428      * @param doRestoreConfig - flag to be passed via Event
 429      *        signaling whether restoring configuration from wd is required
 430      */
 431     protected void updateWorkDir(WorkDirectory wd, boolean doRestoreConfig) {
 432         if (this.wd == wd) {
 433             return; // nothing to change
 434         }
 435         if (this.wd != null) {
 436             throw new IllegalStateException(i18n.getString("bc.resetWorkDir.err"));
 437         }
 438         this.wd = wd;
 439         applyWorkDir(wd);
 440         notifyObservers(new E_NewWD(wd, doRestoreConfig));
 441     }
 442 
 443     /**
 444      * Associates session with the work dir.
 445      * To be overridden when wd should be applied not only to session, but template
 446      * or other properties.
 447      *
 448      * @param wd
 449      */
 450     protected void applyWorkDir(WorkDirectory wd) {
 451         if (config != null) {
 452             config.setWorkDirectory(wd);
 453         }
 454     }
 455 
 456     /**
 457      * Method invoked as a reaction on U_NewConfig update.
 458      * Checks if there are any changes in the update, if none - does nothing,
 459      * Otherwise, copies new values into the main configuration instance,
 460      * notifies observers with E_NewConfig event.
 461      *
 462      * @param ip
 463      * @throws com.sun.javatest.exec.Session.Fault
 464      */
 465     protected void updateNewConfig(InterviewParameters ip) throws Fault {
 466         if (InterviewEditor.equal(ip, this.config) &&
 467                 ip.getFile() != null && ip.getFile().equals(config.getFile())) {
 468             return; // nothing to update
 469         }
 470         try {
 471             InterviewEditor.copy(ip, this.config);
 472         } catch (Exception e) {
 473             throw new Fault(e);
 474         }
 475         notifyObservers(new E_NewConfig(this.config));
 476     }
 477 
 478     /**
 479      * Reloads interview if out of date.
 480      */
 481     public void reloadInterview() throws Fault {
 482         ensureInterviewUpToDate();
 483     }
 484 
 485     void ensureInterviewUpToDate() throws Fault {
 486         try {
 487             if (config.isFileNewer())  {
 488                 config.load();
 489             }
 490         } catch (Exception e) {
 491             throw new Fault(e);
 492         }
 493     }
 494     /**
 495      * @param time Time used in loading, in ms.
 496      * @param wd Work directory associated, may not be null.
 497      * @param msg The message to include with the time, may be null, but usually
 498      *     is the path to the session file that was loaded.
 499      */
 500     private static void logLoadTime(String res,final long time, WorkDirectory wd, String msg) {
 501         if (wd == null)
 502             return;
 503 
 504         Logger log = null;
 505         try {
 506             log = wd.getTestSuite().createLog(wd, null, i18n.getString("exec.log.name"));
 507         }
 508         catch (TestSuite.DuplicateLogNameFault f) {
 509             try {
 510                 log = wd.getTestSuite().getLog(wd, i18n.getString("exec.log.name"));
 511             }
 512             catch (TestSuite.NoSuchLogFault f2) { return; }
 513         }
 514 
 515         if (log != null) {
 516             Integer loadTime = new Integer((int) (time / 1000));
 517             Object[] params = new Object[]{loadTime, msg};
 518             String output = i18n.getString(res, params);
 519             log.info(output);
 520 
 521             if (debug > 0)
 522                 Debug.println(output);
 523         }
 524 
 525     }
 526     private static int debug = Debug.getInt(ExecTool.class);
 527 
 528 }