001    package org.apache.ojb.broker.ant;
002    
003    /* Copyright 2002-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    import java.io.FileInputStream;
018    import java.io.IOException;
019    import java.util.Properties;
020    import javax.xml.parsers.ParserConfigurationException;
021    import javax.xml.parsers.SAXParser;
022    import javax.xml.parsers.SAXParserFactory;
023    import org.apache.ojb.broker.metadata.fieldaccess.PersistentFieldPrivilegedImpl;
024    import org.apache.tools.ant.AntClassLoader;
025    import org.apache.tools.ant.BuildException;
026    import org.apache.tools.ant.Project;
027    import org.apache.tools.ant.Task;
028    import org.apache.tools.ant.types.Path;
029    import org.apache.tools.ant.types.Reference;
030    import org.xml.sax.ContentHandler;
031    import org.xml.sax.SAXException;
032    import org.xml.sax.XMLReader;
033    
034    /**
035     * An Ant task that will read the OJB repository xml file and confirm the
036     * following:
037     * 
038     * a)Mapped classes exist. b)Mapped class fields exist c)Mapped database tables
039     * exist. d)Mapped database columns exist. e)Mapped database columns jdbc type
040     * matches the <code><field-descriptor/></code> "jdbc-type" attribute.
041     * 
042     * Obviously you should have built your classes, written your OJB
043     * repository.xml file and built your database schema before running this task.
044     * 
045     * @author <a href="mailto:daren@softwarearena.com">Daren Drummond</a>
046     * @version $Id: VerifyMappingsTask.java,v 1.1 2007-08-24 22:17:41 ewestfal Exp $
047     */
048    public class VerifyMappingsTask extends Task
049    {
050        private int m_errorCount = 0;
051        private int m_warningCount = 0;
052        private String m_repositoryPath = null;
053        private String m_PropertiesFile = null;
054        private String m_jdbcDriver = null;
055        private String m_url = null;
056        private String m_logon = null;
057        private String m_password = null;
058        private boolean m_ignoreCase = false;
059        private boolean m_UseStrictTypeChecking = true;
060        private Class m_persistenceClass = null;
061        private boolean m_useXMLValidation = true;
062        private boolean m_failOnError = true;
063    
064        private Path _classpath;
065        
066        public void setRepositoryFile(String path)
067        {
068            m_repositoryPath = path;
069        }
070    
071        /**
072         * Gets the path to the OJB repository file. (Includes the file name)
073         * 
074         * @return The path and file name of the OJB repository file
075         */
076        public String getRepositoryFile()
077        {
078            return m_repositoryPath;
079        }
080    
081        /**
082         * Sets the fully qualified path and file name of the OJB properties file.
083         * 
084         * @param path The path and file name of the OJB properties file
085         */
086        public void setPropertiesFile(String path)
087        {
088            m_PropertiesFile = path;
089        }
090    
091        /**
092         * Gets the value set by setPropertiesFile(String path)
093         * 
094         * @return The path and file name of the OJB properties file
095         */
096        public String getPropertiesFile()
097        {
098            return m_PropertiesFile;
099        }
100    
101        /**
102         * Sets the fully qualified class name of the jdbc driver to use.
103         * 
104         * @param jdbcClass Fully qualified class name of the jdbc driver
105         */
106        public void setJdbcDriver(String jdbcClass)
107        {
108            m_jdbcDriver = jdbcClass;
109        }
110    
111        /**
112         * Gets the value set by setJdbcDriver(String jdbcClass)
113         * 
114         * @return Fully qualified class name of the jdbc driver
115         */
116        public String getJdbcDriver()
117        {
118            return m_jdbcDriver;
119        }
120    
121        /**
122         * Sets the url connection string for the jdbc driver.
123         * 
124         * @param url The connection string for the jdbc driver
125         */
126        public void setUrl(String url)
127        {
128            m_url = url;
129        }
130    
131        /**
132         * Gets the value set by setUrl(String url)
133         * 
134         * @return The connection string for the jdbc driver
135         */
136        public String getUrl()
137        {
138            return m_url;
139        }
140    
141        /**
142         * Sets the database logon account that the utility should use.
143         * 
144         * @param logon The database logon account
145         */
146        public void setLogon(String logon)
147        {
148            m_logon = logon;
149        }
150    
151        /**
152         * Gets the value set by setLogon(String logon)
153         * 
154         * @return The database logon account
155         */
156        public String getLogon()
157        {
158            return m_logon;
159        }
160    
161        /**
162         * Sets the password for the database logon account.
163         * 
164         * @param password The password for the database logon account
165         */
166        public void setPassword(String password)
167        {
168            m_password = password;
169        }
170    
171        /**
172         * Gets the value set by setPassword(String password)
173         * 
174         * @return The password for the database logon account
175         */
176        public String getPassword()
177        {
178            return m_password;
179        }
180    
181        /**
182         * Turns on W3C xml validation of the OJB repository.xml file (on by default).
183         * 
184         * @param sValidationFlag Whether to validate the xml
185         */
186        public void setUseXMLValidation(String sValidationFlag)
187        {
188            m_useXMLValidation = Boolean.valueOf(sValidationFlag).booleanValue();
189        }
190    
191        /**
192         * Determines whether the xml syntax is verified.
193         * 
194         * @return A flag indicating if W3c xml validation will be used to
195         *         verify the OJB repository.xml file
196         */
197        public boolean getUseXMLValidation()
198        {
199            return m_useXMLValidation;
200        }
201    
202        /**
203         * Sets a flag indicating that this Ant task should throw a BuildException
204         * if it encounters any verification errors. In most cases, this will have
205         * the effect of stopping the build process.
206         * 
207         * @param sFailFlag Whether to stop the task upon the first error
208         */
209        public void setFailOnError(String sFailFlag)
210        {
211            m_failOnError = Boolean.valueOf(sFailFlag).booleanValue();
212        }
213    
214        /**
215         * Determines whether this task stops by throwing a BuildException when the first
216         * error is encountered.
217         * 
218         * @return A flag indicating that the Ant task will throw a
219         *         BuildException if it encounters any validation errors
220         */
221        public boolean getFailOnError()
222        {
223            return m_failOnError;
224        }
225    
226        /**
227         * Sets the flag for ignoring the db column name case when looking for db
228         * columns.
229         * 
230         * @param sIgnoreCaseFlag Whether the case of the db column name is ignored
231         */
232        public void setIgnoreFieldNameCase(String sIgnoreCaseFlag)
233        {
234            m_ignoreCase = Boolean.valueOf(sIgnoreCaseFlag).booleanValue();
235        }
236    
237        /**
238         * Determines whether the task ignores the case of the db column name case
239         * when looking for db columns. 
240         * 
241         * @return Flag indicating if the field name case will be
242         *         ignored when searching for table column names
243         */
244        public boolean getIgnoreFieldNameCase()
245        {
246            return m_ignoreCase;
247        }
248    
249        /**
250         * Sets the flag for stict type checking of database column types. If this
251         * value is set to "true" then the task will log a warning if the table
252         * column jdbc type doesn't match the type specified in the OJB repository
253         * field descriptor.
254         * 
255         * @param sTypeCheckingFlag Whether to use strict type checking
256         */
257        public void setUseStrictTypeChecking(String sTypeCheckingFlag)
258        {
259            m_UseStrictTypeChecking = Boolean.valueOf(sTypeCheckingFlag)
260            .booleanValue();
261        }
262    
263        /**
264         * Gets the boolean equivalent of the value set by
265         * setUseStrictTypeChecking(String sTypeCheckingFlag)
266         * 
267         * @return Flag indicating if strict type checking will be
268         *         used when searching for database table columns
269         */
270        public boolean getUseStrictTypeChecking()
271        {
272            return m_UseStrictTypeChecking;
273        }
274    
275        /**
276         * Set the classpath for loading the driver.
277         *
278         * @param classpath the classpath
279         */
280        public void setClasspath(Path classpath)
281        {
282            if (_classpath == null)
283            {
284                _classpath = classpath;
285            }
286            else
287            {
288                _classpath.append(classpath);
289            }
290            log("Verification classpath is "+ _classpath,
291                Project.MSG_VERBOSE);
292        }
293    
294        /**
295         * Create the classpath for loading the driver.
296         *
297         * @return the classpath
298         */
299        public Path createClasspath()
300        {
301            if (_classpath == null)
302            {
303                _classpath = new Path(getProject());
304            }
305            return _classpath.createPath();
306        }
307    
308        /**
309         * Set the classpath for loading the driver using the classpath reference.
310         *
311         * @param r reference to the classpath
312         */
313        public void setClasspathRef(Reference r)
314        {
315            createClasspath().setRefid(r);
316            log("Verification classpath is "+ _classpath,
317                Project.MSG_VERBOSE);
318        }
319        
320        public void execute() throws BuildException
321        {
322            if (getRepositoryFile() == null)
323            {    
324                throw new BuildException("Could not find the repository file.");
325            }
326            try
327            {
328                System.setProperty("OJB.properties", getPropertiesFile());
329                //Thread.currentThread().setContextClassLoader(getClassLoader());
330                logWarning("IgnoreFieldNameCase: " + String.valueOf(getIgnoreFieldNameCase()));
331                logWarning("UseStrictTypeChecking: " + String.valueOf(getUseStrictTypeChecking()));
332                logWarning("UseXMLValidation: " + String.valueOf(getUseXMLValidation()));
333                logWarning("UseStrictTypeChecking: " + String.valueOf(getUseStrictTypeChecking()));
334                verifyRepository(getRepositoryFile());
335                logWarning(getSummaryString(getErrorCount(), getWarningCount()));
336            }
337            catch (Exception e)
338            {
339                logWarning("There was an exception while verifying the repsitory: " + e.getMessage());
340                if (getFailOnError())
341                {
342                    throw new BuildException("There was an exception while verifying the repsitory.", e);
343                }
344            }
345            if (getFailOnError() && (getErrorCount() > 0))
346            {
347                throw new BuildException("Failed because 'failonerror' = true and there are " +
348                                         String.valueOf(getErrorCount()) + " mapping error(s).");
349            }
350        }
351    
352        private String getSummaryString(int iBadCount, int iWarningCount)
353        {
354            return "\n---------------------------------------------------\n       Found " +
355                   String.valueOf(iBadCount)+
356                   " error(s) in the repository. \n       Found "+
357                   String.valueOf(iWarningCount)+
358                   " warning(s) in the repository.      \n---------------------------------------------------";
359        }
360    
361        private void verifyRepository(String repositoryFile) throws ParserConfigurationException,
362                                                                    SAXException,
363                                                                    IOException
364        {
365            log("verifyRepository: Entered.");
366    
367            long      start  = System.currentTimeMillis();
368            SAXParser p      = SAXParserFactory.newInstance().newSAXParser();
369            XMLReader reader = p.getXMLReader();
370    
371            reader.setFeature("http://xml.org/sax/features/validation",
372                              getUseXMLValidation());
373    
374            // create handler for verifying the repository structure
375            ContentHandler handler = new RepositoryVerifierHandler(this);
376    
377            logInfo("Starting Parser...");
378            reader.setContentHandler(handler);
379            reader.parse(repositoryFile);
380            logInfo("Done Parsing.");
381    
382            long stop = System.currentTimeMillis();
383    
384            setErrorCount(((RepositoryVerifierHandler)handler).getErrorCount());
385            setWarningCount(((RepositoryVerifierHandler)handler).getWarningCount());
386            logWarning("loading XML took " + (stop - start) + " msecs");
387        }
388    
389        /**
390         * Log a warning with the Ant out stream.
391         * 
392         * @param msg The message to log
393         */
394        public void logWarning(String msg)
395        {
396            log(msg, Project.MSG_WARN);
397        }
398    
399        /**
400         * Log an Info message with the Ant out stream. Info messages can be
401         * suppressed from the command line by starting ant with the -quiet option.
402         * 
403         * @param msg The message to log
404         */
405        public void logInfo(String msg)
406        {
407            log(msg, Project.MSG_INFO);
408        }
409    
410        protected int getErrorCount()
411        {
412            return m_errorCount;
413        }
414    
415        protected void setErrorCount(int count)
416        {
417            m_errorCount = count;
418        }
419    
420        protected int getWarningCount()
421        {
422            return m_warningCount;
423        }
424    
425        protected void setWarningCount(int count)
426        {
427            m_warningCount = count;
428        }
429    
430        /**
431         * Tests to see if the jdbc connection information was specified in the tag
432         * xml.
433         * 
434         * @return <code>true</code> if the jdbc connection information was
435         *         supplied in the tag xml
436         */
437        public boolean hasConnectionInfo()
438        {
439            return (m_jdbcDriver != null && m_url != null && m_logon != null && m_password != null);
440        }
441    
442        Class loadClass(String className) throws ClassNotFoundException
443        {
444            if (_classpath != null)
445            {
446                log("Loading " + className + " using AntClassLoader with classpath " + _classpath,
447                    Project.MSG_VERBOSE);
448    
449                AntClassLoader loader = new AntClassLoader(getProject(), _classpath);
450    
451                return loader.loadClass(className);
452            }
453            else
454            {
455                log("Loading " + className + " using system loader.",
456                    Project.MSG_VERBOSE);
457                return Class.forName(className);
458            }
459        }
460        
461        /**
462         * Returns the Class object of the class specified in the OJB.properties
463         * file for the "PersistentFieldClass" property.
464         * 
465         * @return Class The Class object of the "PersistentFieldClass" class
466         *         specified in the OJB.properties file.
467         */
468        public Class getPersistentFieldClass()
469        {
470            if (m_persistenceClass == null)
471            {
472                Properties properties = new Properties();
473                try
474                {
475                    this.logWarning("Loading properties file: " + getPropertiesFile());
476                    properties.load(new FileInputStream(getPropertiesFile()));
477                }
478                catch (IOException e)
479                {
480                    this.logWarning("Could not load properties file '" + getPropertiesFile()
481                            + "'. Using PersistentFieldDefaultImpl.");
482                    e.printStackTrace();
483                }
484                try
485                {
486                    String className = properties.getProperty("PersistentFieldClass");
487    
488                    m_persistenceClass = loadClass(className);
489                }
490                catch (ClassNotFoundException e)
491                {
492                    e.printStackTrace();
493                    m_persistenceClass = PersistentFieldPrivilegedImpl.class;
494                }
495                logWarning("PersistentFieldClass: " + m_persistenceClass.toString());
496            }
497            return m_persistenceClass;
498        }
499    }