View Javadoc

1   package uk.ac.cam.spectra.spectrasub;
2   
3   import java.beans.PropertyDescriptor;
4   import java.io.File;
5   import java.io.FileInputStream;
6   import java.io.FileOutputStream;
7   import java.io.IOException;
8   import java.lang.reflect.Method;
9   import java.lang.reflect.Modifier;
10  import java.util.ArrayList;
11  import java.util.Collections;
12  import java.util.HashSet;
13  import java.util.List;
14  import java.util.Properties;
15  import java.util.Set;
16  
17  import nu.xom.Attribute;
18  import nu.xom.Builder;
19  import nu.xom.DocType;
20  import nu.xom.Document;
21  import nu.xom.Element;
22  import nu.xom.Node;
23  import nu.xom.Nodes;
24  import nu.xom.ParsingException;
25  import nu.xom.ValidityException;
26  
27  import org.apache.commons.beanutils.PropertyUtils;
28  import org.apache.commons.io.IOUtils;
29  import org.apache.log4j.Logger;
30  import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
31  
32  import uk.ac.cam.spectra.CMLType;
33  import uk.ac.cam.spectra.EmbargoMetadataEncoder;
34  import uk.ac.cam.spectra.Util;
35  
36  /**
37   * Dynamically generates a spring config file for a given workspace and profile.
38   * 
39   * @author ojd20
40   * 
41   */
42  public class Profile2BeanConfig {
43  
44      private static final String DATA_DIR = "data";
45  
46      private final static Logger LOG = Logger
47              .getLogger(Profile2BeanConfig.class);
48  
49      private File profile = null;
50  
51      private File configPropertiesFile = null;
52  
53      private File springBeanFile = null;
54  
55      private File workDir = null;
56  
57      private Set<String> allProperties = new HashSet<String>(200);
58  
59      private Document profileDoc = null;
60  
61      private Document springBeans;
62  
63      private Properties configProperties = new Properties();
64  
65      public void defaultConfiguration(File workspace) {
66          setProfile(new File(workspace, Constants.PROFILE_FILENAME));
67          setConfigPropertiesFile(new File(workspace,
68                  Constants.CONFIG_PROPERTY_FILENAME));
69          setSpringBeanFile(new File(workspace, Constants.SPRING_CONFIG_FILENAME));
70          setWorkDir(new File(workspace, DATA_DIR));
71      }
72  
73      public void writeConfiguration() {
74          FileOutputStream fout = null;
75          try {
76              if (!springBeanFile.exists()) {
77                  springBeanFile.createNewFile();
78              }
79              fout = new FileOutputStream(springBeanFile);
80              Util.print(springBeans, fout, true);
81          } catch (IOException e) {
82              throw new RuntimeException(
83                      "Could not create new spring config file: "
84                              + e.getMessage(), e);
85          } finally {
86              IOUtils.closeQuietly(fout);
87          }
88          FileOutputStream fout2 = null;
89          try {
90              fout2 = new FileOutputStream(configPropertiesFile);
91              Properties toStore = sort(configProperties);
92              toStore.store(fout2, "Auto-generated by Profile2BeanConfig");
93          } catch (IOException e) {
94              throw new RuntimeException("Could not write config.properties: "
95                      + e.getMessage(), e);
96          } finally {
97              IOUtils.closeQuietly(fout2);
98          }
99      }
100 
101     private Properties sort(Properties props) {
102         List<String> keys = new ArrayList<String>(props.size());
103         for (Object key : props.keySet()) {
104             keys.add((String) key);
105         }
106         Collections.sort(keys);
107         Properties p2 = new Properties();
108         for (String key : keys) {
109             p2.setProperty(key, props.getProperty(key));
110         }
111         return p2;
112     }
113 
114     public void init() {
115         if (profile == null) {
116             throw new IllegalStateException("No valid profile provided");
117         }
118 
119         FileInputStream fin = null;
120         try {
121             fin = new FileInputStream(profile);
122             profileDoc = new Builder().build(fin);
123         } catch (ValidityException e) {
124             throw new RuntimeException(e);
125         } catch (ParsingException e) {
126             throw new RuntimeException(e);
127         } catch (IOException e) {
128             throw new RuntimeException(e);
129         } finally {
130             IOUtils.closeQuietly(fin);
131         }
132     }
133 
134     // <?xml version="1.0" encoding="UTF-8"?>
135     // <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
136     // "http://www.springframework.org/dtd/spring-beans.dtd">
137     // <beans default-init-method="init" default-autowire="byType">
138     public void generateConfiguration() throws ConfigurationException {
139         try {
140             Element beans = new Element("beans");
141             springBeans = new Document(beans);
142             springBeans.setDocType(new DocType("beans",
143                     "-//SPRING//DTD BEAN//EN",
144                     "http://www.springframework.org/dtd/spring-beans.dtd"));
145             beans.addAttribute(new Attribute("default-init-method", "init"));
146             beans.addAttribute(new Attribute("default-autowire", "byType"));
147             beans.appendChild(createProfileBean());
148             beans.appendChild(createPropertyPlaceHolderBean());
149             beans.appendChild(createReflectedBean("application", Configuration.class.getCanonicalName()));
150             beans.appendChild(createPackageDAOBean());
151             beans.appendChild(createPackageManager());
152             beans.appendChild(createTypeBean("cml", CMLType.class
153                     .getCanonicalName(), false));
154             Nodes nds = profileDoc.query("//dataFiles/dataFile");
155             for (int i = 0; i < nds.size(); i++) {
156                 Element e = (Element) nds.get(i);
157                 beans.appendChild(createTypeBean(e.getAttributeValue("name"), e
158                         .getAttributeValue("type")));
159             }
160             beans.appendChild(createReflectedBean("embargoEncoder",
161                     EmbargoMetadataEncoder.class.getCanonicalName()));
162             Nodes nds2 = profileDoc.query("//packager|//bundler|//submission");
163             for (int i = 0; i < nds2.size(); i++) {
164                 Element e = (Element) nds2.get(i);
165                 beans.appendChild(createReflectedBean(e.getLocalName(), e
166                         .getAttributeValue("type")));
167             }
168             if (configPropertiesFile != null && configPropertiesFile.exists()) {
169                 FileInputStream fin = null;
170                 try {
171                     fin = new FileInputStream(configPropertiesFile);
172                     configProperties.load(fin);
173                 } catch (IOException e) {
174                     throw new RuntimeException(
175                             "Could not read config.properties: "
176                                     + e.getMessage(), e);
177                 } finally {
178                     IOUtils.closeQuietly(fin);
179                 }
180             } else {
181                 try {
182                     configPropertiesFile.createNewFile();
183                 } catch (IOException e) {
184                     throw new RuntimeException(
185                             "Could not create new config.properties: "
186                                     + e.getMessage(), e);
187                 }
188             }
189             LOG.debug("Adding " + allProperties.size() + " dynamic properties");
190             for (String key : allProperties) {
191                 LOG.debug("Adding property: " + key);
192                 if (!configProperties.containsKey(key)) {
193                     configProperties.setProperty(key, "{Enter value}");
194                 }
195             }
196 
197         } catch (IOException e) {
198             throw new RuntimeException(e);
199         }
200     }
201 
202     // <bean id="profile" class="uk.ac.cam.spectra.spectrasub.Profile">
203     // <property name="file">
204     // <value>/usr/local/spectrasub/cif/profile.xml</value>
205     // </property>
206     // </bean>
207     private Node createProfileBean() throws IOException {
208         Element bean = new Element("bean");
209         bean.addAttribute(new Attribute("id", "profile"));
210         bean.addAttribute(new Attribute("class",
211                 "uk.ac.cam.spectra.spectrasub.Profile"));
212         Element property = new Element("property");
213         property.addAttribute(new Attribute("name", "file"));
214         bean.appendChild(property);
215         Element value = new Element("value");
216         value.appendChild(profile.getCanonicalPath());
217         property.appendChild(value);
218         return bean;
219     }
220 
221     // <bean
222     // class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
223     // <property name="location">
224     // <value>file:///usr/local/spectrasub/cif/config.properties</value>
225     // </property>
226     // </bean>
227     private Node createPropertyPlaceHolderBean() throws IOException {
228         Element bean = new Element("bean");
229         bean.addAttribute(new Attribute("class",
230                 PropertyPlaceholderConfigurer.class.getCanonicalName()));
231         Element property = new Element("property");
232         bean.appendChild(property);
233         property.addAttribute(new Attribute("name", "location"));
234         Element value = new Element("value");
235         property.appendChild(value);
236         value.appendChild(configPropertiesFile.getCanonicalFile().toURL()
237                 .toExternalForm());
238         return bean;
239     }
240 
241     // <bean id="packageDAO"
242     // class="uk.ac.cam.spectra.spectrasub.FSDataPackageDAO">
243     // <property name="storageBase" value="/usr/local/spectrasub/cif/work" />
244     // <property name="type">
245     // <map>
246     // <entry key="CIF">
247     // <ref bean="CIF" />
248     // </entry>
249     // <entry key="CML">
250     // <ref bean="CML" />
251     // </entry>
252     // </map>
253     // </property>
254     // </bean>
255     private Node createPackageDAOBean() throws IOException {
256         Element bean = new Element("bean");
257         bean.addAttribute(new Attribute("id", "packageDAO"));
258         bean.addAttribute(new Attribute("class", DataPackageDAO.class
259                 .getCanonicalName()));
260         Element prop1 = new Element("property");
261         prop1.addAttribute(new Attribute("name", "storageBase"));
262         prop1.addAttribute(new Attribute("value", workDir.getCanonicalPath()));
263         bean.appendChild(prop1);
264         Element prop2 = new Element("property");
265         prop2.addAttribute(new Attribute("name", "type"));
266         bean.appendChild(prop2);
267         Element map = new Element("map");
268         prop2.appendChild(map);
269         addEntry(map, "cml", "uk.ac.cam.spectra.CMLType");
270         Nodes nds = profileDoc.query("//dataFiles/dataFile");
271         for (int i = 0; i < nds.size(); i++) {
272             Element e = (Element) nds.get(i);
273             addEntry(map, e.getAttributeValue("name"), e.getAttributeValue("type"));
274         }
275         return bean;
276     }
277 
278     private void addEntry(Element map, String name, String type) {
279         Element entry = new Element("entry");
280         map.appendChild(entry);
281         entry.addAttribute(new Attribute("key", type));
282         Element ref = new Element("ref");
283         entry.appendChild(ref);
284         ref.addAttribute(new Attribute("bean", name));
285     }
286 
287     // <bean id="packageManager"
288     // class="uk.ac.cam.spectra.spectrasub.DataPackageManager">
289     // </bean>
290     private Element createPackageManager() {
291         Element bean = new Element("bean");
292         bean.addAttribute(new Attribute("id", "packageManager"));
293         bean.addAttribute(new Attribute("class", DataPackageManager.class
294                 .getCanonicalName()));
295         return bean;
296     }
297 
298     private Element createTypeBean(String id, String type) {
299         return createTypeBean(id, type, true);
300     }
301 
302     // <bean id="CIF" class="uk.ac.cam.spectra.cif.CIF" autowire="no" />
303     private Element createTypeBean(String id, String type, boolean autoWire) {
304         Element bean = new Element("bean");
305         bean.addAttribute(new Attribute("id", id));
306         bean.addAttribute(new Attribute("class", type));
307         if (!autoWire) {
308             bean.addAttribute(new Attribute("autowire", "no"));
309         }
310         return bean;
311     }
312 
313     // <bean id="embargoEncoder"
314     // class="uk.ac.cam.spectra.EmbargoMetadataEncoder">
315     // <property name="closedLicenseDescription"
316     // value="${closedLicenseDescription}" />
317     // <property name="openLicenseURL" value="${openLicenseURL}" />
318     // <property name="openLicenseDescription"
319     // value="${openLicenseDescription}" />
320     // </bean>
321     private Element createReflectedBean(String id, String clazz)
322             throws ConfigurationException {
323         Element bean = new Element("bean");
324         bean.addAttribute(new Attribute("id", id));
325         bean.addAttribute(new Attribute("class", clazz));
326         try {
327             Class c = Class.forName(clazz);
328             for (PropertyDescriptor pd : PropertyUtils
329                     .getPropertyDescriptors(c)) {
330                 String pName = pd.getName();
331                 LOG.debug("Property is " + pName + " writable? "
332                         + isWritable(pd));
333                 if (String.class.equals(pd.getPropertyType()) && isWritable(pd)) {
334                     String qName = id + "." + pName;
335                     allProperties.add(qName);
336                     Element property = new Element("property");
337                     bean.appendChild(property);
338                     property.addAttribute(new Attribute("name", pName));
339                     property.addAttribute(new Attribute("value", "${" + qName
340                             + "}"));
341                 }
342             }
343         } catch (ClassNotFoundException e) {
344             throw new ConfigurationException(
345                     "Profile refers to a non-existent class " + clazz + ". "
346                             + e.getMessage(), e);
347         } catch (SecurityException e) {
348             throw new RuntimeException(e);
349         }
350         return bean;
351     }
352 
353     private boolean isWritable(PropertyDescriptor pd) {
354         Method write = pd.getWriteMethod();
355         LOG.debug(pd + " write: " + write);
356         if (write == null) {
357             return false;
358         } else {
359             return Modifier.isPublic(write.getModifiers());
360         }
361     }
362 
363     public File getConfigPropertiesFile() {
364         return configPropertiesFile;
365     }
366 
367     public void setConfigPropertiesFile(File configPropertiesFile) {
368         this.configPropertiesFile = configPropertiesFile;
369     }
370 
371     public File getProfile() {
372         return profile;
373     }
374 
375     public void setProfile(File profile) {
376         this.profile = profile;
377     }
378 
379     public File getSpringBeanFile() {
380         return springBeanFile;
381     }
382 
383     public void setSpringBeanFile(File springBeanFile) {
384         this.springBeanFile = springBeanFile;
385     }
386 
387     public Properties getConfigProperties() {
388         return configProperties;
389     }
390 
391     public Document getSpringBeans() {
392         return springBeans;
393     }
394 
395     public File getWorkDir() {
396         return workDir;
397     }
398 
399     public void setWorkDir(File workDir) {
400         this.workDir = workDir;
401     }
402 
403 }