001    /**
002     * Copyright 2009-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.codehaus.mojo.properties;
017    
018    import java.io.File;
019    import java.io.IOException;
020    import java.text.SimpleDateFormat;
021    import java.util.ArrayList;
022    import java.util.Arrays;
023    import java.util.Collections;
024    import java.util.Date;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Properties;
028    import java.util.Set;
029    
030    import org.apache.commons.io.FileUtils;
031    import org.apache.maven.plugin.MojoExecutionException;
032    import org.apache.maven.plugin.MojoFailureException;
033    import org.codehaus.plexus.util.StringUtils;
034    import org.springframework.util.PropertyPlaceholderHelper;
035    
036    /**
037     * Write project properties to a file.
038     * 
039     * @author Jeff Caddel
040     * 
041     * @goal write-project-properties
042     */
043    public class WriteProjectProperties extends AbstractWritePropertiesMojo {
044            private static final String CR = "\r";
045            private static final String LF = "\n";
046            private static final String TAB = "\t";
047            private static final String BACKSLASH = "\\";
048            private static final String[] ANT_ESCAPE_CHARS = { CR, LF, TAB, BACKSLASH, ":", "#", "=" };
049    
050            /**
051             * Comma separated list of characters to escape when writing property values. cr=carriage return, lf=linefeed, tab=tab. Any other values
052             * are taken literally.
053             * 
054             * @parameter default-value="cr,lf,tab,backslash,:,#,=" expression="${properties.escapeChars}"
055             */
056            private String escapeChars;
057    
058            /**
059             * If true, the plugin will create the properties file formatted the same way Ant formats properties files using the
060             * <code>echoproperties</code> task. This mode adds 3 custom properties at the top of the file, DSTAMP, TODAY, and TSTAMP. In this mode
061             * <code>escapeChars</code> is ignored and the 6 characters Ant escapes are used instead <code>CR</code>,<code>LF</code>,
062             * <code>TAB</code>,<code>:</code>,<code>#</code>,<code>=</code>
063             * 
064             * @parameter default-value="false" expression="${properties.antEchoPropertiesMode}"
065             */
066            private boolean antEchoPropertiesMode;
067    
068            /**
069             * If true, the plugin will include system properties when writing the properties file. System properties override both environment
070             * variables and project properties.
071             * 
072             * @parameter default-value="false" expression="${properties.includeSystemProperties}"
073             */
074            private boolean includeSystemProperties;
075    
076            /**
077             * If true, the plugin will include environment variables when writing the properties file. Environment variables are prefixed with
078             * "env". Environment variables override project properties.
079             * 
080             * @parameter default-value="false" expression="${properties.includeEnvironmentVariables}"
081             */
082            private boolean includeEnvironmentVariables;
083    
084            /**
085             * Comma separated set of properties to exclude when writing the properties file
086             * 
087             * @parameter expression="${properties.exclude}"
088             */
089            private String exclude;
090    
091            /**
092             * Comma separated set of properties to write to the properties file. If provided, only the properties matching those supplied here will
093             * be written to the properties file.
094             * 
095             * @parameter expression="${properties.include}"
096             */
097            private String include;
098    
099            /**
100             * If true placeholders are resolved before writing properties to the file
101             * 
102             * @parameter expression="${properties.resolvePlaceholders}"
103             */
104            private boolean resolvePlaceholders;
105    
106            @Override
107            public void execute() throws MojoExecutionException, MojoFailureException {
108                    Properties properties = new Properties();
109                    // Add project properties
110                    properties.putAll(project.getProperties());
111                    if (includeEnvironmentVariables) {
112                            // Add environment variables, overriding any existing properties with the same key
113                            properties.putAll(getEnvironmentVariables());
114                    }
115                    if (includeSystemProperties) {
116                            // Add system properties, overriding any existing properties with the same key
117                            properties.putAll(System.getProperties());
118                    }
119    
120                    // Remove properties as appropriate
121                    trim(properties, exclude, include);
122    
123                    if (resolvePlaceholders) {
124                            properties = getResolvedProperties(properties);
125                    }
126    
127                    String comment = "# " + new Date() + "\n";
128                    List<String> escapeTokens = getEscapeChars(escapeChars);
129                    if (antEchoPropertiesMode) {
130                            escapeTokens = Arrays.asList(ANT_ESCAPE_CHARS);
131                            comment = getAntHeader();
132                            properties.remove("DSTAMP");
133                            properties.remove("TODAY");
134                            properties.remove("TSTAMP");
135                    }
136    
137                    getLog().info("Creating " + outputFile);
138                    writeProperties(outputFile, comment, properties, escapeTokens);
139            }
140    
141            protected Properties getResolvedProperties(Properties props) {
142                    PropertyPlaceholderHelper pph = new PropertyPlaceholderHelper("${", "}");
143                    List<String> keys = new ArrayList<String>(props.stringPropertyNames());
144                    Collections.sort(keys);
145                    Properties newProps = new Properties();
146                    for (String key : keys) {
147                            String originalValue = props.getProperty(key);
148                            String resolvedValue = pph.replacePlaceholders(originalValue, props);
149                            newProps.setProperty(key, resolvedValue);
150                    }
151                    return newProps;
152    
153            }
154    
155            protected static Properties getEnvironmentVariables() {
156                    String prefix = "env";
157                    Map<String, String> map = System.getenv();
158                    Properties props = new Properties();
159                    for (String key : map.keySet()) {
160                            String newKey = prefix + "." + key;
161                            String value = map.get(key);
162                            props.setProperty(newKey, value);
163                    }
164                    return props;
165            }
166    
167            protected void trim(Properties properties, String excludeCSV, String includeCSV) {
168                    List<String> omitKeys = ReadPropertiesMojo.getListFromCSV(excludeCSV);
169                    for (String key : omitKeys) {
170                            properties.remove(key);
171                    }
172                    if (StringUtils.isBlank(includeCSV)) {
173                            return;
174                    }
175                    List<String> includeKeys = ReadPropertiesMojo.getListFromCSV(includeCSV);
176                    Set<String> keys = properties.stringPropertyNames();
177                    for (String key : keys) {
178                            if (!includeKeys.contains(key)) {
179                                    properties.remove(key);
180                            }
181                    }
182            }
183    
184            protected String getAntHeader() {
185                    SimpleDateFormat dstamp = new SimpleDateFormat("yyyyMMdd");
186                    SimpleDateFormat today = new SimpleDateFormat("MMMM d yyyy");
187                    SimpleDateFormat tstamp = new SimpleDateFormat("HHmm");
188                    Date now = new Date();
189                    StringBuilder sb = new StringBuilder();
190                    sb.append("# Ant properties\n");
191                    sb.append("# " + now + "\n");
192                    sb.append("DSTAMP=" + dstamp.format(now) + "\n");
193                    sb.append("TODAY=" + today.format(now) + "\n");
194                    sb.append("TSTAMP=" + tstamp.format(now) + "\n");
195                    return sb.toString();
196            }
197    
198            protected List<String> getEscapeChars(String escapeChars) {
199                    List<String> tokens = ReadPropertiesMojo.getListFromCSV(escapeChars);
200                    List<String> realTokens = new ArrayList<String>();
201                    for (String token : tokens) {
202                            String realToken = getRealToken(token);
203                            realTokens.add(realToken);
204                    }
205                    return realTokens;
206            }
207    
208            protected String getRealToken(String token) {
209                    if (token.equalsIgnoreCase("CR")) {
210                            return CR;
211                    } else if (token.equalsIgnoreCase("LF")) {
212                            return LF;
213                    } else if (token.equalsIgnoreCase("TAB")) {
214                            return TAB;
215                    } else if (token.equalsIgnoreCase("BACKSLASH")) {
216                            return BACKSLASH;
217                    } else {
218                            return token;
219                    }
220            }
221    
222            protected String getContent(String comment, Properties properties, List<String> escapeTokens) {
223                    List<String> names = new ArrayList<String>(properties.stringPropertyNames());
224                    Collections.sort(names);
225                    StringBuilder sb = new StringBuilder();
226                    if (!StringUtils.isBlank(comment)) {
227                            sb.append(comment);
228                    }
229                    for (String name : names) {
230                            String value = properties.getProperty(name);
231                            String escapedValue = escape(value, escapeTokens);
232                            sb.append(name + "=" + escapedValue + "\n");
233                    }
234                    return sb.toString();
235            }
236    
237            protected void writeProperties(File file, String comment, Properties properties, List<String> escapeTokens) throws MojoExecutionException {
238                    try {
239                            String content = getContent(comment, properties, escapeTokens);
240                            FileUtils.writeStringToFile(file, content);
241                    } catch (IOException e) {
242                            throw new MojoExecutionException("Error creating properties file", e);
243                    }
244            }
245    
246            protected String escape(String s, List<String> escapeChars) {
247                    for (String escapeChar : escapeChars) {
248                            String replacementToken = getReplacementToken(escapeChar);
249                            s = s.replace(escapeChar, replacementToken);
250                    }
251                    return s;
252            }
253    
254            protected String getReplacementToken(String escapeChar) {
255                    if (escapeChar.equals(CR)) {
256                            return "\\r";
257                    } else if (escapeChar.equals(LF)) {
258                            return "\\n";
259                    } else if (escapeChar.equals(TAB)) {
260                            return "\\t";
261                    } else
262                            return "\\" + escapeChar;
263            }
264    
265            public boolean isAntEchoPropertiesMode() {
266                    return antEchoPropertiesMode;
267            }
268    
269            public void setAntEchoPropertiesMode(boolean antEchoPropertiesMode) {
270                    this.antEchoPropertiesMode = antEchoPropertiesMode;
271            }
272    
273            public boolean isIncludeSystemProperties() {
274                    return includeSystemProperties;
275            }
276    
277            public void setIncludeSystemProperties(boolean includeSystemProperties) {
278                    this.includeSystemProperties = includeSystemProperties;
279            }
280    
281            public String getEscapeChars() {
282                    return escapeChars;
283            }
284    
285            public void setEscapeChars(String escapeChars) {
286                    this.escapeChars = escapeChars;
287            }
288    
289            public boolean isIncludeEnvironmentVariables() {
290                    return includeEnvironmentVariables;
291            }
292    
293            public void setIncludeEnvironmentVariables(boolean includeEnvironmentVariables) {
294                    this.includeEnvironmentVariables = includeEnvironmentVariables;
295            }
296    
297            public String getExclude() {
298                    return exclude;
299            }
300    
301            public void setExclude(String exclude) {
302                    this.exclude = exclude;
303            }
304    
305            public String getInclude() {
306                    return include;
307            }
308    
309            public void setInclude(String include) {
310                    this.include = include;
311            }
312    
313            public boolean isResolvePlaceholders() {
314                    return resolvePlaceholders;
315            }
316    
317            public void setResolvePlaceholders(boolean resolvePlaceholders) {
318                    this.resolvePlaceholders = resolvePlaceholders;
319            }
320    }