001/*
002 * Copyright (c) 2003-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: michalo $'
006 * '$Date: 2017-08-22 15:52:22 +0000 (Tue, 22 Aug 2017) $' 
007 * '$Revision: 34612 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.configuration;
031
032import java.io.File;
033import java.util.ArrayList;
034import java.util.Iterator;
035import java.util.List;
036import java.util.Vector;
037
038import org.kepler.build.modules.Module;
039import org.kepler.build.modules.ModuleTree;
040import org.kepler.build.modules.ModulesTxt;
041import org.kepler.util.DotKeplerManager;
042import org.kepler.util.StatusNotifier;
043
044import ptolemy.util.MessageHandler;
045
046/**
047 * A class to manage configuration options in Kepler. For more information see
048 * https://kepler-project.org/developers/teams/framework/kepler-configuration/
049 * proposed-future-kepler-configuration-system
050 * and
051 * https://kepler-project.org/developers/teams/framework/configuration-system-
052 * documentation
053 * 
054 * @author Chad Berkley
055 * @created October 2009
056 */
057public class ConfigurationManager
058{
059  //classes to read and write the configuration
060  protected ConfigurationWriter configWriter;
061  protected ConfigurationReader configReader;
062
063  public static final String dotKeplerConfigurationsDir = 
064      (System.getenv("KEPLER_DOT") != null ? System.getenv("KEPLER_DOT") : System.getProperty("user.home"))
065      + File.separator + ".kepler" + File.separator + "configurations";
066
067  private static ConfigurationManager configurationManager;
068  private List<ConfigurationProperty> propertyList;
069  private Vector<ConfigurationEventListener> listeners;
070
071  /**
072   * private constructor
073   */
074  private ConfigurationManager()
075  {
076    propertyList = new ArrayList<ConfigurationProperty>();
077    listeners = new Vector<ConfigurationEventListener>();
078    //this if block will go away.  it should be replaced by a reflections
079    //method to load the correct configurationreader/writer class.
080    //its here for testing only.
081    //use the general configuration writer which writes dirty properties
082    //to the .kepler directory instead of back to the configuration directory
083    configWriter = new GeneralConfigurationWriter();
084    configReader = new CommonsConfigurationReader(configWriter);
085
086    addConfigurationListener(new SaveListener());
087    
088  }
089
090  /**
091   * singleton accessor
092   */
093  public static ConfigurationManager getInstance()
094  {
095    return getInstance(true);
096  }
097
098  /**
099   * singleton accessor. set loadConfiguration to true if you want to
100   * load the configuration automatically. This singleton accessor should really
101   * only be used for testing.
102   */
103  protected static ConfigurationManager getInstance(boolean loadConfiguration)
104  {
105    if (configurationManager == null)
106    {
107      StatusNotifier.log("Initializing Configuration Manager.");
108      configurationManager = new ConfigurationManager();
109      if(loadConfiguration && ModulesTxt.buildAreaExists()) {
110          try {
111            configurationManager.loadConfiguration();
112        } catch (ConfigurationManagerException e) {
113            MessageHandler.error("Error loading configurations", e);
114        }
115      }
116    }
117    return configurationManager;
118  }
119
120  /**
121   * add a property to the manager
122   * 
123   * @param property the property to add
124   */
125  public void addProperty(RootConfigurationProperty property)
126      throws NamespaceException
127  {
128    ConfigurationNamespace namespace = property.getNamespace();
129    List propList = getProperties(property.getModule());
130    for (int i = 0; i < propList.size(); i++)
131    {
132      RootConfigurationProperty rcp = (RootConfigurationProperty) propList
133          .get(i);
134      if (rcp.getNamespace().equals(namespace))
135      {
136        throw new NamespaceException("Can't add a second namespace '"
137            + namespace + "' to the configuration for module '"
138            + property.getModule().getName() + "'");
139      }
140    }
141    propertyList.add(property);
142  }
143
144  /**
145   * set a list of properties all at once
146   * 
147   * @param propertyList the list of properties to add
148   */
149  public void addProperties(List<RootConfigurationProperty> propertyList)
150      throws NamespaceException
151  {
152    for (int i = 0; i < propertyList.size(); i++)
153    {
154      RootConfigurationProperty rcp = propertyList.get(i);
155      addProperty(rcp);
156    }
157  }
158
159  /**
160   * return all properties handled by the manager
161   */
162  public List<ConfigurationProperty> getProperties()
163  {
164    return propertyList;
165  }
166
167  /**
168   * get a list of properties that belong to a module
169   * 
170   * @param module the module to get the property list for
171   */
172  public List<ConfigurationProperty> getProperties(Module module)
173  {
174    //System.out.println("getting properties for module " + module.getName());
175    Vector<ConfigurationProperty> v = new Vector<ConfigurationProperty>();
176    for (int i = 0; i < propertyList.size(); i++)
177    {
178      ConfigurationProperty prop = propertyList.get(i);
179      if (prop.getModule().getName().equals(module.getName()))
180      {
181        v.add(prop);
182      }
183    }
184    return v;
185  }
186
187  /**
188   * get a list of properties that belong to a certain module within a specific
189   * namespace
190   * 
191   * @param module
192   * @param namespace namespace within the module to get a property list for
193   */
194  public List<ConfigurationProperty> getProperties(Module module,
195      ConfigurationNamespace namespace)
196  {
197    Vector<ConfigurationProperty> v = new Vector<ConfigurationProperty>();
198    List<ConfigurationProperty> l = getProperties(module);
199    for (int i = 0; i < l.size(); i++)
200    {
201      ConfigurationProperty cp = l.get(i);
202      if (cp.getNamespace().equals(namespace))
203      {
204        v.add(cp);
205      }
206    }
207    return v;
208  }
209
210  /**
211   * get a list of properties that belong to a certain module with a specific
212   * name within a namespace
213   * 
214   * @param module
215   * @param namespace namespace within the module
216   * @param name name of the property to get
217   */
218  public List<ConfigurationProperty> getProperties(Module module,
219      ConfigurationNamespace namespace, String name)
220  {
221    Vector<ConfigurationProperty> v = new Vector<ConfigurationProperty>();
222    List<ConfigurationProperty> l = getProperties(module, namespace);
223    for (int i = 0; i < l.size(); i++)
224    {
225      ConfigurationProperty cp = l.get(i);
226      List<ConfigurationProperty> propList = cp.getProperties(name);
227      for (int j = 0; j < propList.size(); j++)
228      {
229        v.add(propList.get(j));
230      }
231    }
232    return v;
233  }
234
235  /**
236   * get a list of properties that belong to a certain module with a specific
237   * name.
238   * this assumes the default namespace.
239   * 
240   * @param module
241   * @param name the name of the property to get
242   */
243  public List<ConfigurationProperty> getProperties(Module module, String name)
244  {
245    return getProperties(module, ConfigurationProperty.namespaceDefault, name);
246  }
247
248  /**
249   * Returns the root property of the default configuration for a module.
250   * This should be stored in
251   * a file named "configuration.xml" in the
252   * &lt;module&gt;/resources/configurations
253   * directory. If no such file exists, this will return null.
254   * 
255   * @param module the module to get the default configuration for.
256   */
257  public ConfigurationProperty getProperty(Module module)
258  {
259    return getProperty(module, ConfigurationProperty.namespaceDefault);
260  }
261
262  /**
263   * return the root ConfigurationProperty of the namespace.
264   * 
265   * @param module the module to get the property from
266   * @param namespace the namespace to get the property from
267   */
268  public ConfigurationProperty getProperty(Module module,
269      ConfigurationNamespace namespace)
270  {
271    List l = getProperties(module);
272    for (int i = 0; i < l.size(); i++)
273    {
274      RootConfigurationProperty rcp = (RootConfigurationProperty) l.get(i);
275
276      if (rcp.getNamespace().equals(namespace))
277      {
278        return rcp.getRootProperty();
279      }
280    }
281    return null;
282  }
283
284  /**
285   * get a property from the module's default namespace configuration with
286   * a given name
287   * 
288   * @param module the module to get the property from
289   * @param name the name of the property to get.
290   */
291  public ConfigurationProperty getProperty(Module module, String name)
292  {
293    return getProperty(module, ConfigurationProperty.namespaceDefault, name);
294  }
295
296  /**
297   * return a single property from a module with a specific name in a specific
298   * namespace. If more than one property shares a name, the first is returned.
299   * Return null if the property is not found.
300   * 
301   * @param module the module to get the property from
302   * @param namespace the namespace to get the property from
303   * @param name the name of the property to get
304   */
305  public ConfigurationProperty getProperty(Module module,
306      ConfigurationNamespace namespace, String name)
307  {
308    ConfigurationProperty cp = getProperty(module, namespace);
309    if (cp == null){
310        return null;
311    }
312    ConfigurationProperty prop = cp.getProperty(name);
313    return prop;
314  }
315
316  /**
317   * serialize the entire configuration
318   */
319  public void saveConfiguration() throws ConfigurationManagerException
320  {
321    for (int i = 0; i < propertyList.size(); i++)
322    {
323      RootConfigurationProperty prop = (RootConfigurationProperty) propertyList
324          .get(i);
325      configWriter.writeConfiguration(prop);
326    }
327  }
328
329  /**
330   * add a configuration listener
331   * 
332   * @param listener the listener to add
333   */
334  public void addConfigurationListener(ConfigurationEventListener listener)
335  {
336    listeners.add(listener);
337  }
338
339  /**
340   * remove a listener
341   * 
342   * @param listener the listener to remove
343   */
344  public void removeConfigurationListener(ConfigurationEventListener listener)
345  {
346    for (int i = 0; i < listeners.size(); i++)
347    {
348      ConfigurationEventListener cel = listeners.get(i);
349      if (cel == listener)
350      {
351        listeners.remove(i);
352      }
353    }
354  }
355
356  /**
357   * returns the file from the .kepler directory if it exists, returns f if
358   * it doesn't. This method should be used to get configuration files
359   * because if the file has changed it will be written back to .kepler, not
360   * back to the resources/configurations directory of the module.
361   * 
362   * @param m the module to get the overwrite file from
363   * @param f the file to get
364   */
365  public static File getOverwriteFile(Module m, File f)
366  {
367    File dotKeplerConfDir = DotKeplerManager.getInstance()
368        .getModuleConfigurationDirectory(m.getName());
369    ///         .getModuleConfigurationDirectory(m.getStemName());
370    if (!dotKeplerConfDir.exists())
371    {
372      return f;
373    }
374
375    String[] files = dotKeplerConfDir.list();
376    for (int j = 0; j < files.length; j++)
377    {
378      File confFile = new File(dotKeplerConfDir, files[j]);
379      if (confFile.getName().equals(f.getName()))
380      {
381        //found it.  return it.
382        return confFile;
383      }
384    }
385    return f;
386  }
387
388  /**
389   * get a module by name. return null if not found
390   * 
391   * @param name the name of the module to get
392   */
393  public static Module getModule(String name)
394  {
395    Module m = ModuleTree.instance().getModuleByStemName(name);
396    return m;
397  }
398
399  /**
400   * notify any listeners that a configuration event has occured
401   * 
402   * @param property the property that changed to send to the listeners
403   */
404  protected void notifyListeners(ConfigurationProperty property)
405  {
406    for (int i = 0; i < listeners.size(); i++)
407    {
408      ConfigurationEventListener listener = listeners.get(i);
409      listener.eventPerformed(new ConfigurationEvent(property));
410    }
411  }
412
413  /**
414   * deserialize the configuration
415   */
416  protected void loadConfiguration() throws ConfigurationManagerException
417  {
418    ModuleTree tree = ModuleTree.instance();
419    Iterator it = tree.iterator();
420    while (it.hasNext())
421    {
422      Module m = (Module) it.next();
423      //System.out.println("loading files from module " + m.getName());
424      List<RootConfigurationProperty> v = configReader.loadConfigurations(m);
425      
426      if (v == null)
427      {
428        continue;
429      }
430      for (int i = 0; i < v.size(); i++)
431      {
432        ConfigurationProperty prop = v.get(i);
433        prop.resetDirty(true);
434        //System.out.println("adding prop to prop list: " + prop.toString(false));
435        propertyList.add(prop);
436      }
437    }
438    //check to see if any properties of a module have the same namespace and 
439    //if so, merge them into one property
440    resolveNamespaces();
441  }
442
443  /**
444   * remove all configurations currently listed and reset the configuration
445   * manager back to its un-initialized state. This should really only be used
446   * for testing.
447   */
448  protected void clearConfigurations()
449  {
450    configurationManager = null;
451    propertyList = null;
452    listeners = null;
453  }
454
455  /**
456   * utility method to remove the file extension from the filename
457   * 
458   * @param filename the filename to remove the extension from
459   */
460  protected static String removeFileExtension(String filename)
461  {
462    String s = filename.substring(0, filename.lastIndexOf("."));
463    return s;
464  }
465
466  /**
467   * remove the locale designator from a filename. this works with
468   * or without a file extension (i.e. .xml). This method assumes
469   * a locale with both a country and language code (i.e. _en_US).
470   */
471  protected static String removeLocaleDesignator(String filename)
472  {
473    String fn = filename;
474    int i = filename.lastIndexOf("_");
475    if (i != -1)
476    {
477      filename = filename.substring(0, i);
478      i = filename.lastIndexOf("_");
479      if (i != -1)
480      {
481        filename = filename.substring(0, i);
482        return filename;
483      }
484    }
485
486    return fn;
487  }
488
489  /**
490   * check to see if any properties of a module have the same namespace and
491   * if so, merge them into one property
492   */
493  private void resolveNamespaces()
494  {
495    //ConfigurationProperty.simplePrintList(getProperties());
496    ModuleTree tree = ModuleTree.instance();
497    Iterator it = tree.iterator();
498    while (it.hasNext())
499    {
500      boolean done = false;
501      Module m = (Module) it.next();
502      List props = getProperties(m);
503      for (int i = 0; i < props.size(); i++)
504      { //go through the properties and check to see if any have the same namespace
505        ConfigurationProperty cp = (ConfigurationProperty) props.get(i);
506        for (int j = 0; j < props.size(); j++)
507        {
508          ConfigurationProperty cp2 = (ConfigurationProperty) props.get(j);
509          if (i != j)
510          {
511            if (cp.getNamespace().equals(cp2.getNamespace()))
512            { //same namespace is found.  merge them and get out of the loops
513              RootConfigurationProperty rcp = new RootConfigurationProperty(m,
514                  cp.getNamespace().toString(), cp.getNamespace());
515              try
516              {
517                rcp.addProperty(((RootConfigurationProperty) cp)
518                    .getRootProperty());
519                rcp.addProperty(((RootConfigurationProperty) cp2)
520                    .getRootProperty());
521                propertyList.remove(cp);
522                propertyList.remove(cp2);
523                propertyList.add(rcp);
524                done = true;
525                break;
526              }
527              catch (Exception e)
528              {
529                System.out.println("Error merging namesapace properties.  "
530                    + "This exception should not be happening: "
531                    + e.getMessage());
532              }
533            }
534          }
535        }
536
537        if (done)
538        {
539          break;
540        }
541      }
542    }
543  }
544}