View Javadoc

1   /*
2   Karma core - Core of the Karma application
3   Copyright (C) 2004  Toolforge <www.toolforge.nl>
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19  package nl.toolforge.karma.core.boot;
20  
21  import nl.toolforge.karma.core.ErrorCode;
22  import nl.toolforge.karma.core.location.Location;
23  import nl.toolforge.karma.core.location.LocationDescriptor;
24  import nl.toolforge.karma.core.location.LocationException;
25  import nl.toolforge.karma.core.location.LocationFactory;
26  import org.apache.commons.digester.Digester;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.xml.sax.SAXException;
30  
31  import java.io.BufferedWriter;
32  import java.io.File;
33  import java.io.FileWriter;
34  import java.io.IOException;
35  import java.io.Writer;
36  import java.text.MessageFormat;
37  import java.util.ArrayList;
38  import java.util.Enumeration;
39  import java.util.Iterator;
40  import java.util.List;
41  import java.util.Properties;
42  
43  /***
44   * <p>Configuration class for a <code>WorkingContext</code>. A <code>WorkingContextConfiguration</code> consists of the
45   * following configuration:
46   *
47   * <ul>
48   *   <li>Properties that can be used by Karma. Three properties are mandatory :
49   *     <ul>
50   *       <li>
51   *         <code>project.baseDir</code>, indicating the directory where projects for a working context are stored.
52   *       </li>
53   *       <li>
54   *         <code>manifest-store.module</code>, which is the module name for the manifest store in a version control
55   *         system. Note that the format of the module name might be version control system dependent.
56   *       </li>
57   *       <li>
58   *         <code>location-store.module</code>, which is the module name for the location store in a version control
59   *         system. Note that the format of the module name might be version control system dependent.
60   *       </li>
61   *     </ul>
62   *   <li/>
63   *   <li>A manifest store <code>Location</code>, describing the version control system where the
64   *       <code>manifest-store.module</code> module can be found.
65   *   <li/>
66   *   <li>A location store <code>Location</code>, describing the version control system where the
67   *       <code>location-store.module</code> module can be found.
68   *   <li/>
69   * </ul>
70   *
71   * <p>The configuration is not loaded automatically. By calling the {@link #load()}-method the configuration will be
72   * loaded.
73   *
74   * @author D.A. Smedes
75   * @version $Id: WorkingContextConfiguration.java,v 1.6 2004/11/03 20:54:14 asmedes Exp $
76   */
77  public final class WorkingContextConfiguration {
78  
79    public static final ErrorCode CONFIGURATION_LOAD_ERROR = new ErrorCode("WCC-00001");
80  
81    private static Log logger = LogFactory.getLog(WorkingContextConfiguration.class);
82  
83  //  private File workingContextConfigurationFile = null;
84  
85    private ManifestStore manifestStore = null;
86    private LocationStore locationStore = null;
87  
88    private Properties configuration = null;
89  
90    private WorkingContext workingContext = null;
91  
92  
93  
94    /***
95     * Creates a configuration object using <code>configFile</code> as the configuration file. The configuration is
96     * loaded by calling {@link #load()}.
97     *
98     * @param workingContext The <code>WorkingContext</code> for this configuration.
99     */
100   public WorkingContextConfiguration(WorkingContext workingContext) {
101 
102     if (workingContext == null) {
103       throw new IllegalArgumentException("Working context cannot be null.");
104     }
105 
106     this.workingContext = workingContext;
107     configuration = new Properties();
108   }
109 
110   private File getConfigFile() {
111     return new File(workingContext.getWorkingContextConfigurationBaseDir(), "working-context.xml");
112   }
113 
114   /***
115    * Returns the <code>LocationStore</code> for this configuration. Can be <code>null</code> if not configured properly.
116    *
117    * @return The <code>LocationStore</code> for this configuration. Can be <code>null</code> if not configured properly.
118    */
119   public LocationStore getLocationStore() {
120     return locationStore;
121   }
122 
123   /***
124    * Returns the <code>ManifestStore</code> for this configuration. Can be <code>null</code> if not configured properly.
125    *
126    * @return The <code>ManifestStore</code> for this configuration. Can be <code>null</code> if not configured properly.
127    */
128   public ManifestStore getManifestStore() {
129     return manifestStore;
130   }
131 
132   /***
133    * Sets the manifest store for this configuration. <code>null</code>s are allowed.
134    *
135    * @param manifestStore The manifest store for this configuration.
136    */
137   public void setManifestStore(ManifestStore manifestStore) {
138     this.manifestStore = manifestStore;
139   }
140 
141   /***
142    * Sets the location store for this configuration. <code>null</code>s are allowed.
143    *
144    * @param locationStore The location store for this configuration.
145    */
146   public void setLocationStore(LocationStore locationStore) {
147     this.locationStore = locationStore;
148   }
149 
150 
151   /***
152    *
153    * @param key
154    * @return <code>null</code> if the property is not found.
155    */
156   public String getProperty(String key) {
157 
158     if (configuration == null) {
159       return null;
160     }
161 
162     return configuration.getProperty(key);
163   }
164 
165 
166 
167   /***
168    * Adds or changes a property in the current configuration. When a property with key <code>name</code> exists, its
169    * value is overwritten with <code>value</code>.
170    */
171   public void setProperty(String name, String value) {
172     configuration.setProperty(name, value);
173   }
174 
175 
176 
177   /***
178    * Thorough checks of this configuration is valid, and if it is not, returns the <code>ErrorCode</code> to indicate
179    * what went wrong or <code>null</code> if nothing went wrong, and this configuration is ready to use.
180    *
181    * @return An <code>ErrorCode</code> indicating the exact failure or <code>null</code> of nothing it wrong.
182    */
183   public ErrorCode check() {
184 
185     try {
186       if (load()) {
187         // ok
188       }
189     } catch (WorkingContextException e) {
190       return CONFIGURATION_LOAD_ERROR;
191     }
192 
193     return null;
194   }
195 
196 
197 
198 
199 
200   /***
201    * <p>Loads the configuration from <code>working-context.xml</code>. When the file did not exists, <code>false</code>
202    * will be returned. Otherwise, this method returns <code>true</code> and the configuration succeeded. Note that
203    * this method performs no validation on the configuration itself. This is left to the user.
204    *
205    * <p>When the configuration could be loaded, this method returns <code>true</code>. This is not to say that the
206    * configuration is correct. The configuration should still be checked for correctness by the client.
207    *
208    * <p>Calling this method overwrites any properties already set for this configuration.
209    *
210    * @return                         <code>true</code> when the configuration could be loaded, <code>false</code> if it
211    *                                 couldn't.
212    *
213    * @throws WorkingContextException When an <code>IOException</code> or <code>SAXException</code> occurs, indicating
214    *                                 an error when reading a configuration file, which could result by the client in
215    *                                 specific actions (thus the exception and not the <code>true</code> or
216    *                                 <code>false</code>.
217    */
218   public boolean load() throws WorkingContextException {
219 
220     configuration = new Properties();
221 
222     // Read properties
223     //
224     Digester propertyDigester = getPropertyDigester();
225 
226     List properties = null;
227 
228     try {
229       properties = (List) propertyDigester.parse(getConfigFile());
230     } catch (SAXException e) {
231       logger.error(e);
232       throw new WorkingContextException("XML error in configuration for working context `" + workingContext + "`. ", e);
233     } catch (IOException e) {
234       logger.error(e);
235       throw new WorkingContextException(e);
236     }
237 
238     for (Iterator i = properties.iterator(); i.hasNext();) {
239       Property property = (Property) i.next();
240       configuration.put(property.getName(), property.getValue());
241     }
242 
243     // Read manifests-store and location-store data
244     //
245     Digester locationDigester = LocationDescriptor.getDigester();
246 
247     List locations = null;
248 
249     try {
250       locations = (List) locationDigester.parse(getConfigFile());
251     } catch (SAXException e) {
252       logger.error(e);
253       throw new WorkingContextException("XML error in configuration for working context `" + workingContext + "`. ", e);
254     } catch (IOException e) {
255       logger.error(e);
256       throw new WorkingContextException(e);
257     }
258 
259     if (locations == null) {
260       return false;
261     }
262 
263     // Expecting exactly two items !!! Only 'manifest-store' and 'location-store' are accepted.
264     //
265     for (Iterator i = locations.iterator(); i.hasNext();) {
266 
267       LocationDescriptor descriptor = (LocationDescriptor) i.next();
268 
269       Location location = null;
270       try {
271         location = LocationFactory.getInstance().createLocation(descriptor);
272       } catch (LocationException e) {
273         return false;
274       }
275 
276       location.setWorkingContext(workingContext);
277 
278       if ("manifest-store".equals(location.getId())) {
279 
280         String moduleName = (String) configuration.get(WorkingContext.MANIFEST_STORE_MODULE);
281         manifestStore = new ManifestStore(workingContext, moduleName, location);
282 
283       } else if ("location-store".equals(location.getId())) {
284 
285         String moduleName = (String) configuration.get(WorkingContext.LOCATION_STORE_MODULE);
286         locationStore = new LocationStore(workingContext, moduleName, location);
287 
288       } else {
289         logger.error(
290             "Invalid location element in `working-context.xml`. " +
291             "Expecting a `manifest-store` and `location-store` entry.");
292         return false;
293       }
294     }
295 
296     if (manifestStore == null || locationStore == null) {
297       return false;
298     }
299 
300     return true;
301   }
302 
303   /***
304    * Stores the all configuration items in <code>configuration</code> in the <code>working-context.xml</code> file.
305    *
306    * @throws WorkingContextException When storing failed.
307    */
308   public void store() throws WorkingContextException {
309 
310     // todo this implementation can be made more efficient, but it's ok for now.
311 
312     if (configuration == null) {
313       throw new NullPointerException("Configuration cannot be null.");
314     }
315 
316     // Check the (mandatory) configuration
317     //
318 
319     StringBuffer buffer = new StringBuffer();
320     buffer.append("<?xml version=\"1.0\"?>\n");
321 
322     buffer.append(
323         "<wc:working-context\n" +
324         "  xmlns:wc=\"http://www.toolforge.org/specifications/working-context\"\n" +
325         "  xmlns:loc=\"http://www.toolforge.org/specifications/location\">\n");
326 
327     // <properties>-element
328     //
329     buffer.append("  <wc:properties>\n");
330 
331     MessageFormat formatter = new MessageFormat("    <wc:property name=\"{0}\" value=\"{1}\"/>\n");
332 
333     Enumeration e = configuration.propertyNames();
334 
335     while (e.hasMoreElements()) {
336 
337       String key = (String) e.nextElement();
338       String value = configuration.getProperty(key);
339 
340       buffer.append(formatter.format(new String[]{key, value}));
341     }
342 
343     buffer.append("  </wc:properties>\n\n");
344     //
345     // </properties>-element
346 
347     if (manifestStore == null && locationStore == null) {
348       //
349     } else {
350 
351       buffer.append("  <loc:locations>\n");
352 
353       if (manifestStore != null) {
354         buffer.append(manifestStore.getLocation().asXML());
355       }
356       if (locationStore != null) {
357         buffer.append(locationStore.getLocation().asXML());
358       }
359 
360       buffer.append("  </loc:locations>\n");
361 
362     }
363 
364     buffer.append("\n");
365 
366     buffer.append("</wc:working-context>\n");
367 
368     // Write to file `working-context.xml`
369     //
370     try {
371       if (!getConfigFile().exists()) {
372         getConfigFile().createNewFile();
373       }
374       Writer writer = new BufferedWriter(new FileWriter(getConfigFile()));
375       writer.write(buffer.toString());
376       writer.flush();
377     } catch (IOException ioe) {
378       logger.error(ioe);
379       throw new WorkingContextException(ioe);
380     }
381   }
382 
383 
384 
385   /*
386   * The working-context configuration file is populated with elements from two different namespaces. One is the
387   * working context stuff itself (all sorts of properties), the other is from the <code>location</code> namespace,
388   * configuration for the manifest store and the location store.
389   *
390   * <p>This method gets a <code>Digester</code> for the general properties for the working context.
391   */
392   private Digester getPropertyDigester() {
393 
394     Digester digester = new Digester();
395 
396     digester.setNamespaceAware(true);
397 
398     digester.addObjectCreate("working-context/properties", ArrayList.class);
399 
400     digester.addObjectCreate("working-context/properties/property", Property.class);
401     digester.addSetProperties("working-context/properties/property");
402     digester.addSetNext("working-context/properties/property", "add");
403 
404     return digester;
405   }
406 }