View Javadoc
1   /**
2    * Copyright (c) 2013-2017 Polago AB
3    * All rights reserved.
4    *
5    * Permission is hereby granted, free of charge, to any person obtaining
6    * a copy of this software and associated documentation files (the
7    * "Software"), to deal in the Software without restriction, including
8    * without limitation the rights to use, copy, modify, merge, publish,
9    * distribute, sublicense, and/or sell copies of the Software, and to
10   * permit persons to whom the Software is furnished to do so, subject to
11   * the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be
14   * included in all copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21   * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23   */
24  
25  package org.polago.deployconf.task.properties;
26  
27  import java.io.BufferedWriter;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.io.OutputStreamWriter;
32  import java.util.LinkedHashSet;
33  import java.util.Set;
34  
35  import org.jdom2.Element;
36  import org.polago.deployconf.InteractiveConfigurer;
37  import org.polago.deployconf.group.ConfigGroup;
38  import org.polago.deployconf.group.ConfigGroupManager;
39  import org.polago.deployconf.task.AbstractTask;
40  import org.polago.deployconf.task.Task;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * Deployment Task for creating properties in a file.
46   */
47  public class PropertiesTask extends AbstractTask {
48  
49      private static Logger logger = LoggerFactory.getLogger(PropertiesTask.class);
50  
51      /**
52       * Task element name in config file.
53       */
54      public static final String DOM_ELEMENT_TASK = "properties";
55  
56      private static final String DOM_ELEMENT_PROPERTY = "property";
57  
58      protected static final String DOM_ELEMENT_NAME = "name";
59  
60      // Properties files are always using ISO-8859-1
61      private static final String ENCODING = "ISO-8859-1";
62  
63      private static final String PATH_IGNORE = PropertiesTask.class.getCanonicalName() + "_NO_PATH";;
64  
65      private Set<Property> properties;
66  
67      /**
68       * Public Constructor.
69       *
70       * @param groupManager the groupManager to use
71       */
72      public PropertiesTask(ConfigGroupManager groupManager) {
73          super(groupManager);
74          properties = new LinkedHashSet<Property>();
75      }
76  
77      /**
78       * {@inheritDoc}
79       */
80      @Override
81      public void deserialize(Element node) throws IOException {
82          String attribute = node.getAttributeValue(DOM_ATTRIBUTE_PATH);
83          if (attribute == null) {
84              attribute = PATH_IGNORE;
85          }
86          setPath(attribute);
87  
88          for (Element e : node.getChildren()) {
89              String name = e.getChildTextTrim(DOM_ELEMENT_NAME);
90              if (name.length() == 0) {
91                  throw new IllegalStateException("Property name element does not exists");
92              }
93              String description = e.getChildTextTrim(DOM_ELEMENT_DESCRIPTION);
94              if (description.length() == 0) {
95                  throw new IllegalStateException("Property description element does not exists");
96              }
97              String defaultValue = e.getChildTextTrim(DOM_ELEMENT_DEFAULT);
98  
99              String group = e.getAttributeValue(DOM_ATTRIBUTE_GROUP);
100             String value = null;
101 
102             if (group != null) {
103                 value = getGroupManager().lookupGroup(group).getProperty(name);
104             }
105 
106             if (value == null) {
107                 value = e.getChildTextTrim(DOM_ELEMENT_VALUE);
108                 if (group != null && value != null) {
109                     logger.debug("Populating group {} with value of name {}: {}", group, name, value);
110                     getGroupManager().lookupGroup(group).setProperty(name, value);
111                 }
112             }
113 
114             Property p = new Property(name, description, defaultValue, value);
115 
116             if (group != null) {
117                 p.setGroup(group);
118             }
119 
120             p.setCondition(e.getChildTextTrim(DOM_ELEMENT_CONDITION));
121 
122             logger.debug("Deserializing property: {}", p);
123 
124             properties.add(p);
125         }
126     }
127 
128     /**
129      * {@inheritDoc}
130      */
131     @Override
132     public void serialize(Element node) throws IOException {
133         if (!PATH_IGNORE.equals(getPath())) {
134             node.setAttribute(DOM_ATTRIBUTE_PATH, getPath());
135         }
136 
137         for (Property p : properties) {
138             logger.debug("Serializing property: {}", p);
139             Element e = createJDOMElement(DOM_ELEMENT_PROPERTY);
140             e.addContent(createJDOMTextElement(DOM_ELEMENT_NAME, p.getName()));
141             e.addContent(createJDOMCDATAElement(DOM_ELEMENT_DESCRIPTION, p.getDescription()));
142             e.addContent(createJDOMTextElement(DOM_ELEMENT_DEFAULT, p.getDefaultValue()));
143             e.addContent(createJDOMTextElement(DOM_ELEMENT_CONDITION, p.getCondition()));
144 
145             String group = p.getGroup();
146             if (group != null) {
147                 e.setAttribute(DOM_ATTRIBUTE_GROUP, group);
148             } else {
149                 e.addContent(createJDOMTextElement(DOM_ELEMENT_VALUE, p.getValue()));
150             }
151 
152             node.addContent(e);
153         }
154     }
155 
156     /**
157      * Gets the properties property value.
158      *
159      * @return the current value of the properties property
160      */
161     public Set<Property> getProperties() {
162         return properties;
163     }
164 
165     /**
166      * Sets the properties property.
167      *
168      * @param properties the new property value
169      */
170     void setProperties(Set<Property> properties) {
171         this.properties = properties;
172     }
173 
174     /**
175      * {@inheritDoc}
176      */
177     @Override
178     public void merge(Task other) {
179         if (other instanceof PropertiesTask) {
180             PropertiesTask opt = (PropertiesTask) other;
181             properties.retainAll(opt.getProperties());
182 
183             for (Property op : opt.getProperties()) {
184                 boolean exists = false;
185                 for (Property p : properties) {
186                     if (p.equals(op)) {
187                         exists = true;
188                         p.setDescription(op.getDescription());
189                         p.setDefaultValue(op.getDefaultValue());
190                         p.setGroup(op.getGroup());
191                         p.setCondition(op.getCondition());
192                         logger.debug("Merging existing Property: {}", p);
193                         break;
194                     }
195                 }
196                 if (!exists) {
197                     logger.debug("Adding new Property: {}", op);
198                     properties.add(op);
199                 }
200             }
201         }
202     }
203 
204     /**
205      * {@inheritDoc}
206      */
207     @Override
208     public boolean isConfigured() throws IOException {
209         for (Property p : properties) {
210             if (evaluateCondition(p.getCondition(), getGroupManager().lookupGroup(p.getGroup()))) {
211                 if (p.getValue() == null || p.getValue().length() == 0) {
212                     logger.debug("Property is not configured: {}", p);
213                     return false;
214                 }
215             }
216         }
217 
218         return true;
219     }
220 
221     /**
222      * {@inheritDoc}
223      */
224     @Override
225     public boolean configureInteractively(InteractiveConfigurer configurer, boolean force) throws Exception {
226 
227         boolean configured = true;
228 
229         for (Property p : properties) {
230             ConfigGroup group = getGroupManager().lookupGroup(p.getGroup());
231             if (evaluateCondition(p.getCondition(), group)
232                 && (force || p.getValue() == null || p.getValue().length() == 0)) {
233                 configured = configurePropertyInteractively(p, configurer);
234                 if (configured) {
235                     group.setProperty(p.getName(), p.getValue());
236                 } else {
237                     return configured;
238                 }
239             }
240         }
241 
242         return configured;
243     }
244 
245     /**
246      * {@inheritDoc}
247      */
248     @Override
249     public String getSerializedName() {
250         return DOM_ELEMENT_TASK;
251     }
252 
253     /**
254      * {@inheritDoc}
255      */
256     @Override
257     public void apply(InputStream source, OutputStream destination) throws Exception {
258 
259         if (PATH_IGNORE.equals(getPath())) {
260             // This task should never be applied
261             return;
262         }
263 
264         OutputStreamWriter out = new OutputStreamWriter(destination, ENCODING);
265         BufferedWriter writer = new BufferedWriter(out);
266 
267         for (Property p : getProperties()) {
268             ConfigGroup group = getGroupManager().lookupGroup(p.getGroup());
269             if (evaluateCondition(p.getCondition(), group)) {
270                 writer.newLine();
271                 String description = p.getDescription();
272                 if (description != null) {
273                     writer.append("#");
274                     writer.newLine();
275                     String[] lines = description.split("[\\r\\n]+");
276                     for (String line : lines) {
277                         writer.append("# ");
278                         writer.append(line.trim());
279                         writer.newLine();
280                     }
281                     writer.append("#");
282                     writer.newLine();
283                 }
284                 writer.append(p.getName());
285                 writer.append("=");
286 
287                 String value = expandPropertyExpression(p.getValue(), group);
288                 writer.append(value);
289                 writer.newLine();
290             }
291         }
292         writer.flush();
293     }
294 
295     /**
296      * Configure a Property by asking the user.
297      *
298      * @param p the Property to configure
299      * @param configurer the InteractiveConfigurer to use
300      * @return true if the property was configured
301      * @throws IOException indicating IO failure
302      */
303     private boolean configurePropertyInteractively(Property p, InteractiveConfigurer configurer) throws IOException {
304 
305         boolean result = false;
306 
307         logger.debug("Configure interactively: {}", p.getName());
308 
309         String defaultValue = p.getValue();
310         if (defaultValue == null || defaultValue.length() == 0) {
311             defaultValue = p.getDefaultValue();
312         }
313 
314         String value = configurer.configure(p.getName(), p.getDescription(), defaultValue);
315 
316         logger.debug("Configure interactively result for '{}': {}", p.getName(), value);
317 
318         if (value != null) {
319             p.setValue(value);
320             result = true;
321         }
322 
323         return result;
324     }
325 
326     /**
327      * {@inheritDoc}
328      */
329     @Override
330     public String toString() {
331         return "PropertiesTask [path=" + getPath() + ", properties=" + properties + "]";
332     }
333 
334 
335 }