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.FileInputStream;
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.util.ArrayList;
023    import java.util.List;
024    import java.util.Properties;
025    import java.util.Set;
026    
027    import org.apache.commons.io.IOUtils;
028    import org.apache.commons.lang.StringUtils;
029    import org.apache.maven.plugin.AbstractMojo;
030    import org.apache.maven.plugin.MojoExecutionException;
031    import org.apache.maven.project.MavenProject;
032    import org.codehaus.plexus.util.cli.CommandLineUtils;
033    import org.springframework.core.io.DefaultResourceLoader;
034    import org.springframework.core.io.Resource;
035    import org.springframework.core.io.ResourceLoader;
036    
037    /**
038     * The read-project-properties goal reads property files and stores the properties as project properties. It serves as
039     * an alternate to specifying properties in pom.xml.
040     *
041     * @author <a href="mailto:zarars@gmail.com">Zarar Siddiqi</a>
042     * @author <a href="mailto:Krystian.Nowak@gmail.com">Krystian Nowak</a>
043     * @version $Id: ReadPropertiesMojo.java 8861 2009-01-21 15:35:38Z pgier $
044     * @goal read-project-properties
045     */
046    public class ReadPropertiesMojo extends AbstractMojo {
047    
048        /**
049         * @parameter default-value="${project}"
050         * @required
051         * @readonly
052         */
053        private MavenProject project;
054    
055        /**
056         * Locations where properties files can be found. Any url Spring resource loading can understand is valid. eg
057         * <code>classpath:myprops.properties</code>. Both, .properties and .xml style properties are supported.
058         *
059         * @parameter
060         * @required
061         */
062        private String[] locations;
063    
064        /**
065         * If true, the plugin will silently ignore any non-existent properties files, and the build will continue
066         *
067         * @parameter expression="${properties.quiet}" default-value="false"
068         */
069        private boolean quiet;
070    
071        /**
072         * If true, the plugin operate silently without emitting any log messages
073         *
074         * @parameter expression="${properties.silent}" default-value="false"
075         */
076        private boolean silent;
077    
078        /**
079         * If true, the plugin will emit more verbose logging messages.
080         *
081         * @parameter expression="${properties.verbose}" default-value="false"
082         */
083        private boolean verbose;
084    
085        /**
086         * Comma separated list of property values to ignore
087         *
088         * @parameter expression="${properties.ignore}"
089         */
090        private String ignore;
091    
092        @Override
093        public void execute() throws MojoExecutionException {
094            List<String> ignoreList = getListFromCSV(ignore);
095            Properties projectProperties = project.getProperties();
096            if (!silent && verbose && !StringUtils.isBlank(ignore)) {
097                getLog().info("Ignoring " + ignore);
098            }
099            for (int i = 0; i < locations.length; i++) {
100                String location = locations[i];
101                if (!validate(location)) {
102                    continue;
103                }
104                if (!silent) {
105                    getLog().info("Loading " + location);
106                }
107                Properties p = getProperties(location);
108                updateProperties(projectProperties, p, ignoreList);
109            }
110    
111            Properties env = getEnvironment();
112            for (String name : projectProperties.stringPropertyNames()) {
113                String value = getPropertyValue(name, projectProperties, env);
114                projectProperties.setProperty(name, value);
115            }
116        }
117    
118        protected Properties getEnvironment() throws MojoExecutionException {
119            try {
120                return CommandLineUtils.getSystemEnvVars();
121            } catch (IOException e) {
122                throw new MojoExecutionException("Error get environment variables", e);
123            }
124        }
125    
126        protected void updateProperties(Properties p1, Properties p2, List<String> ignore) {
127            Set<String> names = p2.stringPropertyNames();
128            for (String name : names) {
129                if (!ignore.contains(name)) {
130                    String value = p2.getProperty(name);
131                    p1.setProperty(name, value);
132                }
133            }
134        }
135    
136        protected static final List<String> getListFromCSV(String csv) {
137            if (StringUtils.isBlank(csv)) {
138                return new ArrayList<String>();
139            }
140            List<String> list = new ArrayList<String>();
141            String[] tokens = StringUtils.split(csv, ",");
142            for (String token : tokens) {
143                list.add(token.trim());
144            }
145            return list;
146        }
147    
148        /**
149         * Retrieves a property value, replacing values like ${token} using the Properties to look them up. Shamelessly
150         * adapted from:
151         * http://maven.apache.org/plugins/maven-war-plugin/xref/org/apache/maven/plugin/war/PropertyUtils.html
152         *
153         * It will leave unresolved properties alone, trying for System properties, and environment variables and implements
154         * reparsing (in the case that the value of a property contains a key), and will not loop endlessly on a pair like
155         * test = ${test}
156         *
157         * @param k
158         *            property key
159         * @param p
160         *            project properties
161         * @param environment
162         *            environment variables
163         * @return resolved property value
164         */
165        protected String getPropertyValue(String k, Properties p, Properties environment) {
166            String v = p.getProperty(k);
167            String ret = "";
168            int idx, idx2;
169    
170            while ((idx = v.indexOf("${")) >= 0) {
171                // append prefix to result
172                ret += v.substring(0, idx);
173    
174                // strip prefix from original
175                v = v.substring(idx + 2);
176    
177                idx2 = v.indexOf("}");
178    
179                // if no matching } then bail
180                if (idx2 < 0) {
181                    break;
182                }
183    
184                // strip out the key and resolve it
185                // resolve the key/value for the ${statement}
186                String nk = v.substring(0, idx2);
187                v = v.substring(idx2 + 1);
188                String nv = p.getProperty(nk);
189    
190                // try global environment
191                if (nv == null) {
192                    nv = System.getProperty(nk);
193                }
194    
195                // try environment variable
196                if (nv == null && nk.startsWith("env.") && environment != null) {
197                    nv = environment.getProperty(nk.substring(4));
198                }
199    
200                // if the key cannot be resolved,
201                // leave it alone ( and don't parse again )
202                // else prefix the original string with the
203                // resolved property ( so it can be parsed further )
204                // taking recursion into account.
205                if (nv == null || nv.equals(nk)) {
206                    ret += "${" + nk + "}";
207                } else {
208                    v = nv + v;
209                }
210            }
211            return ret + v;
212        }
213    
214        protected String toEmpty(String s) {
215            if (StringUtils.isBlank(s)) {
216                return "";
217            } else {
218                return s;
219            }
220        }
221    
222        protected boolean exists(String location) {
223            if (StringUtils.isBlank(location)) {
224                return false;
225            }
226            File file = new File(location);
227            if (file.exists()) {
228                return true;
229            }
230            ResourceLoader loader = new DefaultResourceLoader();
231            Resource resource = loader.getResource(location);
232            return resource.exists();
233        }
234    
235        protected boolean validate(String location) throws MojoExecutionException {
236            boolean exists = exists(location);
237            if (exists) {
238                return true;
239            }
240            if (quiet) {
241                if (verbose && !silent) {
242                    getLog().info("Ignoring non-existent properties file '" + toEmpty(location) + "'");
243                }
244                return false;
245            } else {
246                throw new MojoExecutionException("Non-existent properties file '" + location + "'");
247            }
248        }
249    
250        protected InputStream getInputStream(String location) throws IOException {
251            File file = new File(location);
252            if (file.exists()) {
253                return new FileInputStream(location);
254            }
255            ResourceLoader loader = new DefaultResourceLoader();
256            Resource resource = loader.getResource(location);
257            return resource.getInputStream();
258        }
259    
260        protected Properties getProperties(String location) throws MojoExecutionException {
261            InputStream in = null;
262            try {
263                Properties properties = new Properties();
264                in = getInputStream(location);
265                if (location.toLowerCase().endsWith(".xml")) {
266                    properties.loadFromXML(in);
267                } else {
268                    properties.load(in);
269                }
270                return properties;
271            } catch (IOException e) {
272                throw new MojoExecutionException("Error reading properties file " + location, e);
273            } finally {
274                IOUtils.closeQuietly(in);
275            }
276        }
277    
278        public boolean isQuiet() {
279            return quiet;
280        }
281    
282        public void setQuiet(boolean quiet) {
283            this.quiet = quiet;
284        }
285    
286        public String getIgnore() {
287            return ignore;
288        }
289    
290        public void setIgnore(String ignoreProperties) {
291            this.ignore = ignoreProperties;
292        }
293    
294        public MavenProject getProject() {
295            return project;
296        }
297    
298        public String[] getLocations() {
299            return locations;
300        }
301    
302        public void setLocations(String[] locations) {
303            this.locations = locations;
304        }
305    
306        public boolean isVerbose() {
307            return verbose;
308        }
309    
310        public void setVerbose(boolean verbose) {
311            this.verbose = verbose;
312        }
313    
314        public boolean isSilent() {
315            return silent;
316        }
317    
318        public void setSilent(boolean silent) {
319            this.silent = silent;
320        }
321    
322    }