View Javadoc

1   /**
2    * Copyright 2005-2006 the original author or authors.
3    *
4    * Licensed under the Gnu General Pubic License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with
6    * the License. You may obtain a copy of the License at
7    *
8    *      http://www.opensource.org/licenses/gpl-license.php
9    *
10   * This program 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.
13   * See the Gnu General Public License for more details.
14   */
15  package org.figure8.join.services.scripting.ant.tasks;
16  
17  import org.figure8.join.services.remoting.ParameterService;
18  import org.figure8.join.services.remoting.InvalidSessionException;
19  import org.figure8.join.services.remoting.beans.RemoteParameter;
20  import org.figure8.join.services.security.InvalidLoginException;
21  
22  import org.apache.tools.ant.Project;
23  import org.apache.tools.ant.BuildException;
24  import org.apache.tools.ant.DirectoryScanner;
25  import org.apache.tools.ant.types.FileSet;
26  
27  import java.io.File;
28  import java.io.BufferedReader;
29  import java.io.FileReader;
30  import java.io.BufferedWriter;
31  import java.io.FileWriter;
32  import java.util.Map;
33  import java.util.List;
34  import java.util.HashMap;
35  import java.util.ArrayList;
36  import java.util.regex.Pattern;
37  import java.util.regex.Matcher;
38  import java.rmi.RemoteException;
39  /**
40   * This is an Ant task to use for substituting deployment parameters, into source files,
41   * by their values according to environment and deployment target.<br/> As an example,
42   * use a <i>substituteParams</i> that way to ensure substitution :<br/>
43   * <code>
44   * &gt;substituteParams environmentKey="env01" targetName="full" regExp="\\$(\\w+)\\$"
45   *       toDir="${dest.dir}"&lt;<br/>
46   *    &gt;fileset dir="${src.dir}" includes="*.properties"/&lt;<br/>
47   *    &gt;fileset dir="${src2.dir}" includes="*.xml"/&lt;<br/>
48   * &gt;substituteParams/&lt;<br/>
49   * </code><br/>
50   * This task connects to Join server in order to dynamically retrieve the parameters
51   * name/value pairs for environment "env01" and target "full". It then uses the regular
52   * expression pattern "\$(\w+)\$" (this is its default value and you may omit it - be
53   * careful to double backslashes for Java string) to find the parameter names into source
54   * files contained into filesets.
55   * <br/>
56   * By default, if an exception occurs or if a found parameter has no value, the task
57   * produces a "SubstituteTask.log" log file into destination directory. You may change
58   * the name of this log file using the <i>log</i> attribute of task. If you may also want
59   * the task to fail on error, set the <i>failonerror</i> attribute to true.
60   * <br/>
61   * @author <a href="mailto:laurent.broudoux@free.fr">Laurent Broudoux</a>
62   * @version $Revision: 1.2 $
63   */
64  public class SubstituteParametersTask extends RemoteServiceTask{
65  
66     // Static -------------------------------------------------------------------
67  
68     /** Constant representing the default regular expression used : \$(\w+)\$ */
69     public static final String DEFAULT_REGEXP = "\\$(\\w+)\\$";
70     /** Constant representing the default name for log file */
71     public static final String DEFAULT_LOGFILE = "SubstituteTask.log";
72  
73  
74     // Attributes ---------------------------------------------------------------
75  
76     /** The key of physical environment to use for retrieving parameter references */
77     private String environmentKey;
78     /** The name of deployment target to use for retrieving parameter references */
79     private String targetName;
80     /** The regular expression pattern for selecting parameters within files */
81     private String regExp = DEFAULT_REGEXP;
82     /** The destination directory for substituted files */
83     private String toDir;
84     /** The name of log file where to write errors or warning */
85     private String logFile = DEFAULT_LOGFILE;
86     /** Flag telling if substitution exceptions thrown a BuildException */
87     private boolean failonerror = false;
88  
89     /** A list of Ant filesets to process */
90     private ArrayList fileSets = new ArrayList();
91  
92     /** The file representation of toDir attribute */
93     private File destinationDir;
94  
95  
96     // Constructors -------------------------------------------------------------
97  
98     /** Creates a new instance of SubstituteParametersTask */
99     public SubstituteParametersTask(){
100    }
101 
102 
103    // Public -------------------------------------------------------------------
104 
105    /** @return The key of physical environment to use for retrieving parameter references */
106    public String getEnvironmentKey(){
107       return environmentKey;
108    }
109    /** @param environmentKey The key of physical environment to use for retrieving parameter references */
110    public void setEnvironmentKey(String environmentKey){
111       this.environmentKey = environmentKey;
112    }
113 
114    /** @return The name of deployment target to use for retrieving parameter references */
115    public String getTargetName(){
116       return targetName;
117    }
118    /** @param targetName The name of deployment target to use for retrieving parameter references */
119    public void setTargetName(String targetName){
120       this.targetName = targetName;
121    }
122 
123    /** @return The regular expression pattern for selecting parameters within files */
124    public String getRegExp(){
125       return regExp;
126    }
127    /** @param regExp The regular expression pattern for selecting parameters within files */
128    public void setRegExp(String regExp){
129       this.regExp = regExp;
130    }
131 
132    /** @return The destination directory for processed files */
133    public String getToDir(){
134       return toDir;
135    }
136    /** @param toDir The destination directory for processed files */
137    public void setToDir(String toDir){
138       this.toDir = toDir;
139    }
140 
141    /** @return The name of log file where to write errors or warning */
142    public String getLogFile(){
143       return logFile;
144    }
145    /** @param logFile The name of log file where to write errors or warning */
146    public void setLogFile(String logFile){
147       this.logFile = logFile;
148    }
149 
150    /** @return Flag telling if substitution exceptions thrown a BuildException */
151    public boolean isFailonerror(){
152       return failonerror;
153    }
154    /** @param failonerror Flag telling if substitution exceptions thrown a BuildException */
155    public void setFailonerror(boolean failonerror){
156       this.failonerror = failonerror;
157    }
158 
159    /**
160     * Adds a set of files (nested fileset element)
161     * @param set A FileSet containing files to process
162     */
163    public void addFileset(FileSet set){
164       fileSets.add(set);
165    }
166 
167 
168    // Override of Task ---------------------------------------------------------
169 
170    /**
171     * Execute this task : do the substitution according into configured filesets
172     * @throws BuildException if something wrong occurs during task process
173     */
174    public void execute() throws BuildException{
175       // Validate task attributes.
176       validateAttributes();
177       // Retrieve the parameters map.
178       Map parametersMap = retrieveParametersMap();
179       // Do the substitution job.
180       doSubstitution(parametersMap);
181    }
182 
183 
184    // Protected ----------------------------------------------------------------
185 
186    /**
187     * Validate this task attributes before execution
188     * @throws BuildException if a mandatory attribute is not present
189     */
190    protected void validateAttributes() throws BuildException{
191       // Check that environmentKey is present.
192       if (environmentKey == null || environmentKey.length() == 0)
193          throw new BuildException("No environment key specified for substituteParameters", getLocation());
194       // Check that targetName is present.
195       if (targetName == null || targetName.length() == 0)
196          throw new BuildException("No deployment target specified for substituteParameters", getLocation());
197       // Check that destination dir is present.
198       if (toDir == null || toDir.length() == 0)
199          throw new BuildException("No destination directory specified for substituteParameters", getLocation());
200       // Check that destination dir is existing.
201       destinationDir = new File(toDir);
202       if (!destinationDir.isDirectory())
203          throw new BuildException("The destination directory specified is not a valid directory", getLocation());
204       // Check that there's at least one fileset.
205       if (fileSets.isEmpty())
206          throw new BuildException("No FileSet specified for substituteParameters", getLocation());
207    }
208 
209    /**
210     * Connect to remote service for retrieving a map containing parameters.
211     * @throws BuildException
212     */
213    protected Map retrieveParametersMap() throws BuildException{
214       // Retrieve remote service, security token and parameters map.
215       ParameterService service = (ParameterService)retrieveRemoteService("ParameterService", ParameterService.class);
216 
217       String token = null;
218       HashMap parametersMap = new HashMap();
219       try{
220          // Login and invoke service.
221          token = loginToRemoteService(service);
222          RemoteParameter[] parameters = service.getDeploymentParameters(token, environmentKey, targetName);
223          // Build the name/value parametersMap.
224          for (int i=0; i<parameters.length; i++)
225             parametersMap.put(parameters[i].getName(), parameters[i].getValue());
226       }
227       catch (InvalidLoginException ile){
228          // Log diagnostic messages and wrap into a BuildException.
229          log("InvalidLoginException while logging to remote service: " + ile.getMessage(), Project.MSG_ERR);
230          throw new BuildException("Exception while logging to remote service", ile, getLocation());
231       }
232       catch (InvalidSessionException ise){
233          // Log diagnostic messages and wrap into a BuildException.
234          log("InvalidSessionException while invoking remote service: " + ise.getMessage(), Project.MSG_ERR);
235          throw new BuildException("Exception while invoking remote service", ise, getLocation());
236       }
237       catch (RemoteException re){
238          // Log diagnostic messages and wrap into a BuildException.
239          log("RemoteException while logging to remote service: " + re.getMessage(), Project.MSG_ERR);
240          throw new BuildException("Exception while logging to remote service", re, getLocation());
241       }
242       // Release service and return.
243       releaseRemoteService(service, token);
244       return parametersMap;
245    }
246 
247    /**
248     * Do the substitution using the given parameters map into the configured filesets
249     * @throws BuildException if exception occurs during log file writing or if errors
250     * and failonerror falgf is set to true
251     */
252    protected void doSubstitution(Map parametersMap) throws BuildException{
253       // Prepare the regular expression pattern and global messages.
254       ArrayList globalMessages = new ArrayList();
255       Pattern pattern = Pattern.compile(regExp);
256 
257       // Browse filesets for doing subsitution into files.
258       for (int i=0; i<fileSets.size(); i++){
259          FileSet set = (FileSet)fileSets.get(i);
260          DirectoryScanner scanner = set.getDirectoryScanner(getProject());
261 
262          File sourceDir = set.getDir(getProject());
263          String[] fileNames = scanner.getIncludedFiles();
264 
265          // Do substitution in each file, storing messages.
266          for (int j=0; i<fileNames.length; i++){
267             File src = new File(sourceDir, fileNames[i]);
268             File dest = new File(destinationDir, fileNames[j]);
269             List messages = doSubstitutionInFile(src, dest, pattern, parametersMap);
270             // Store errors messages if any.
271             if (messages != null && !messages.isEmpty())
272                globalMessages.addAll(messages);
273          }
274       }
275 
276       // Write error messages.
277       if (!globalMessages.isEmpty()){
278          BufferedWriter writer = null;
279          try{
280             // Write each error message on log file.
281             writer = new BufferedWriter(new FileWriter(new File(destinationDir, logFile)));
282             for (int i=0; i<globalMessages.size(); i++){
283                String message = (String)globalMessages.get(i);
284                writer.write(message);
285                writer.newLine();
286             }
287          }
288          catch (Exception e){
289             // Log and raise a build exception.
290             log("Exception while writing log for substitution: " + e.getMessage(), Project.MSG_ERR);
291             throw new BuildException("Exception while writing log for substitution: " + e.getMessage());
292          }
293          finally{
294             try {writer.close();}
295             catch (Exception e) {log("Exception while closing writer", Project.MSG_DEBUG);}
296          }
297 
298          if (failonerror){
299             // Log and raise a build exception.
300             log("Exception while doing parameters substitution. Check " + logFile + " log file!", Project.MSG_ERR);
301             throw new BuildException("Exception while doing parameters substitution. Check " + logFile + " log file!");
302          }
303       }
304    }
305 
306    /**
307     * Do the substitution work from a source file into a destination file
308     * @param src The source file to substitute parameters in
309     * @param dest The destination file to write with parameters values
310     * @param pattern The regular expression pattern for selecting parameters into <b>src</b>
311     * @param parametersMap The map containing parameter name/value pairs
312     * @return A list containing substitution error messages (may be empty)
313     */
314    protected List doSubstitutionInFile(File src, File dest, Pattern pattern, Map parametersMap){
315       // Prepare reader, writer and a result list.
316       BufferedReader reader = null;
317       BufferedWriter writer = null;
318       ArrayList result = new ArrayList();
319 
320       try{
321          // Prepare reader and writer.
322          reader = new BufferedReader(new FileReader(src));
323          writer = new BufferedWriter(new FileWriter(dest));
324 
325          String line = null;
326          // Browse source file for parameters
327          while ((line = reader.readLine()) != null){
328             Matcher matcher = pattern.matcher(line);
329             while (matcher.lookingAt()){
330                String param = matcher.group(1);
331                String value = (String)parametersMap.get(param);
332                // If it's a known param ...
333                if (value != null){
334                   // ... replace first occurence of regexp and reset.
335                   line = matcher.replaceFirst(value);
336                   matcher.reset(line);
337                }
338                else{
339                   // Log a diagnostic message.
340                   log("Parameter '" + param + "' is unknown", Project.MSG_DEBUG);
341                   result.add("Parameter '" + param + "' is unknown");
342                }
343             }
344             // Write line and carriage return before starting next line.
345             writer.write(line);
346             writer.newLine();
347          }
348       }
349       catch (Exception e){
350          // Log a diagnostic message.
351          log("Exception while doing substitution into file: " + src.getPath(), Project.MSG_WARN);
352          result.add("Exception while doing substitution into file: " + src.getPath());
353       }
354       finally{
355          try {writer.close();}
356          catch (Exception e) {log("Exception while closing writer", Project.MSG_DEBUG);}
357          try {reader.close();}
358          catch (Exception e) {log("Exception while closing reader", Project.MSG_DEBUG);}
359       }
360       // Return messages.
361       return result;
362    }
363 }