001package org.andromda.maven.plugin.andromdapp.hibernate;
002
003import java.io.File;
004import java.lang.reflect.Method;
005import java.sql.Connection;
006import java.util.ArrayList;
007import java.util.Arrays;
008import java.util.List;
009import java.util.Map;
010import org.andromda.core.common.ClassUtils;
011import org.andromda.maven.plugin.andromdapp.SchemaManagement;
012import org.andromda.maven.plugin.andromdapp.SchemaManagementException;
013import org.apache.commons.lang.ObjectUtils;
014import org.apache.commons.lang.StringUtils;
015import org.apache.log4j.Logger;
016import org.codehaus.plexus.util.DirectoryScanner;
017
018/**
019 * A Hibernate management object.
020 *
021 * @author Chad Brandon
022 */
023public abstract class HibernateSchemaManagement
024    implements SchemaManagement
025{
026    /**
027     * getLogger(HibernateSchemaManagement.class)
028     */
029    protected static Logger logger = Logger.getLogger(HibernateSchemaManagement.class);
030
031    /**
032     * The Hibernate 2 schema export class name.
033     */
034    protected static final String HIBERNATE_2_PACKAGE = "net.sf.hibernate.tool.hbm2ddl";
035
036    /**
037     * The Hibernate 3 schema export class name.
038     */
039    protected static final String HIBERNATE_3_PACKAGE = "org.hibernate.tool.hbm2ddl";
040
041    /**
042     * The Hibernate version
043     */
044    private String version;
045
046    /**
047     * Sets the version of Hibernate to target.
048     *
049     * @param version the version of Hibernate to target.
050     */
051    public void setVersion(final String version)
052    {
053        this.version = version;
054    }
055
056    /**
057     * Retrieves the class that performs the execution work.
058     *
059     * @return the Hibernate class that performs the work.
060     */
061    protected Class getExecutionClass()
062    {
063        Class hibernateClass = null;
064        String hibernate2ClassName = null;
065        String hibernate3ClassName = null;
066        try
067        {
068            hibernate3ClassName = HIBERNATE_3_PACKAGE + '.' + this.getExecutionClassName();
069            hibernateClass = ClassUtils.loadClass(hibernate3ClassName);
070        }
071        catch (Exception exception)
072        {
073            // - ignore, means we can't find the Hibernate 3 class
074        }
075        try
076        {
077            hibernate2ClassName = HIBERNATE_2_PACKAGE + '.' + this.getExecutionClassName();
078            hibernateClass = ClassUtils.loadClass(hibernate2ClassName);
079        }
080        catch (Exception exception)
081        {
082            // - ignore, means we can't find the Hibernate 2 class
083        }
084        if (hibernateClass == null)
085        {
086            throw new RuntimeException(
087                "There appear to be no Hibernate 2 or 3 jars are your classpath, because neither '" +
088                hibernate2ClassName + "', nor '" + hibernate3ClassName + "' could be found");
089        }
090        return hibernateClass;
091    }
092
093    /**
094     * Returns the current Hibernate version this management object
095     * is targetting.
096     *
097     * @return the Hibernate version (2 or 3).
098     */
099    protected String getVersion()
100    {
101        return this.version;
102    }
103
104    /**
105     * Stores the path to which output should be written.
106     */
107    private String outputPath;
108
109    /**
110     * Sets the path to which the output should be written.
111     *
112     * @param outputPath
113     */
114    public void setOutputPath(final String outputPath)
115    {
116        this.outputPath = outputPath;
117    }
118
119    /**
120     * Attempts to retrieve the given property with the given <code>name</code>.
121     *
122     * @param properties the properties from which to retrieve the property.
123     * @param name the name of the property to retrieve.
124     * @return the value of the property.
125     */
126    protected String getProperty(
127        final Map properties,
128        final String name)
129    {
130        final String value = ObjectUtils.toString(properties.get(name));
131        return value;
132    }
133
134    /**
135     * Attempts to retrieve the given property with the given <code>name</code>.
136     * If the property isn't found an exception is thrown.
137     *
138     * @param properties the properties from which to retrieve the property.
139     * @param name the name of the property to retrieve.
140     * @return the value of the property.
141     */
142    protected String getRequiredProperty(
143        final Map properties,
144        final String name)
145    {
146        final String value = ObjectUtils.toString(properties.get(name));
147        if (StringUtils.isBlank(value))
148        {
149            throw new SchemaManagementException("The '" + name + "' must be specified");
150        }
151        return value;
152    }
153
154    /**
155     * Gets the path to which output should be written.
156     *
157     * @return the output path.
158     */
159    protected String getOutputPath()
160    {
161        return this.outputPath;
162    }
163
164    /**
165     * Returns the name of the class that performs the execution.
166     *
167     * @return the execution class.
168     */
169    protected abstract String getExecutionClassName();
170
171    /**
172     * @see org.andromda.maven.plugin.andromdapp.SchemaManagement#execute(java.sql.Connection, java.util.Map)
173     */
174    public String execute(
175        Connection connection,
176        java.util.Map options)
177        throws Exception
178    {
179        final String hibernateDialect = "hibernate.dialect";
180        System.setProperty(
181            hibernateDialect,
182            this.getRequiredProperty(
183                options,
184                hibernateDialect));
185        final List<String> argumentList = this.getArguments(options);
186        final String[] arguments = argumentList.toArray(new String[argumentList.size()]);
187        final Class executionClass = this.getExecutionClass();
188        final Method method = executionClass.getMethod(
189                "main",
190                new Class[] {String[].class});
191        method.invoke(
192            executionClass,
193            new Object[] {arguments});
194
195        return this.getExecutionOuputPath(options);
196    }
197
198    /**
199     * Returns the path of the execution output file.
200     *
201     * @param options the options from which to retrieve properties.
202     * @return the output path.
203     */
204    protected abstract String getExecutionOuputPath(final Map options);
205
206    /**
207     * Retrieves the arguments common to all Hibernate schema management
208     * tasks.
209     *
210     * @param options the options from which to retrieve any required properties.
211     * @return the list of common arguments.
212     */
213    private List<String> getArguments(final Map options)
214        throws Exception
215    {
216        final List<String> mappingFiles =
217            this.getMappingFilesList(
218                this.getRequiredProperty(
219                    options,
220                    "mappingFileExtension"),
221                this.getRequiredProperty(
222                    options,
223                    "mappingsLocation"));
224        final String[] args = new String[] {"--delimiter=;", "--format"};
225        final List<String> arguments = new ArrayList<String>(Arrays.asList(args));
226        arguments.addAll(mappingFiles);
227        this.addArguments(
228            options,
229            arguments);
230        return arguments;
231    }
232
233    /**
234     * Adds any arguments required by the specialized class.
235     *
236     * @param options any options from which to retrieve argument values.
237     * @param arguments the list of arguments to add.
238     * @throws Exception
239     */
240    protected abstract void addArguments(
241        final Map options,
242        final List<String> arguments) throws Exception;
243
244    /**
245     * Retrieves all mapping files having the given <code>extension</code>
246     * existing in the <code>baseDirectory</code> or any of its sub directories.
247     *
248     * @param extension the mapping file extension
249     * @param baseDirectory the directory from which to perform the search.
250     * @return the list of mapping files
251     */
252    protected List<String> getMappingFilesList(
253        final String extension,
254        final String baseDirectory)
255    {
256        final DirectoryScanner scanner = new DirectoryScanner();
257        scanner.setBasedir(baseDirectory);
258        scanner.setIncludes(new String[] {"**/*." + extension});
259        scanner.setExcludes(null);
260        scanner.scan();
261
262        final List<String> files = new ArrayList<String>();
263        for (String path : Arrays.asList(scanner.getIncludedFiles()))
264        {
265            files.add(new File(
266                    baseDirectory,
267                    path).toString());
268        }
269        return files;
270    }
271}