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    
018    import java.lang.reflect.Constructor;
019    import java.lang.reflect.InvocationTargetException;
020    import java.net.MalformedURLException;
021    import java.sql.Driver;
022    import java.sql.DriverManager;
023    import java.sql.SQLException;
024    import java.sql.SQLWarning;
025    import java.util.ArrayList;
026    import java.util.Collection;
027    
028    import org.apache.ojb.broker.PersistenceBrokerException;
029    import org.apache.ojb.broker.locking.IsolationLevels;
030    import org.apache.ojb.broker.metadata.*;
031    import org.apache.ojb.broker.util.logging.Logger;
032    import org.apache.ojb.broker.util.logging.LoggerFactory;
033    import org.xml.sax.Attributes;
034    import org.xml.sax.SAXException;
035    import org.xml.sax.SAXParseException;
036    import org.xml.sax.helpers.DefaultHandler;
037    
038    /**
039     * A SAX parser handler used by the Ant VerifyMappingsTask to process an
040     * OJB DescriptorRepository xml file.
041     *
042     * @author <a href="mailto:daren@softwarearena.com">Daren Drummond</a>
043     * @version $Id: RepositoryVerifierHandler.java,v 1.1 2007-08-24 22:17:41 ewestfal Exp $
044     */
045    public class RepositoryVerifierHandler
046        extends DefaultHandler
047        implements RepositoryElements, IsolationLevels
048    {
049        private Logger logger;
050    
051        private DescriptorRepository m_repository;
052        private JdbcConnectionDescriptor m_CurrentJCD;
053        private ClassDescriptor m_CurrentCLD;
054        private FieldDescriptor m_CurrentFLD;
055        private ObjectReferenceDescriptor m_CurrentORD;
056        private CollectionDescriptor m_CurrentCOD;
057        private String m_CurrentString;
058        private VerifyMappingsTask m_callingTask;
059        private DBUtility m_dBUtility;
060    
061        //ClassDescriptor members;
062        //private String m_CurrentClass = "";
063        private Class m_currentClass = null;
064        private String m_CurrentTable = null;
065        private boolean m_currentTableExists = false;
066    
067        /** the default isolation level*/
068        private int defIsoLevel = IL_DEFAULT;
069    
070        /**
071         * All known xml tags are kept in this table.
072         * The tags table allows lookup from literal to id
073         * and from id to literal.
074         */
075        private RepositoryTags tags = RepositoryTags.getInstance();
076    
077        private Collection m_VerifyExceptions = new ArrayList(69);
078        private Collection m_VerifyWarnings = new ArrayList(69);
079        
080        /**
081         * Allows not to specify field id.
082         */
083        private int m_lastId = 0;
084    
085    
086        /**
087         * The only public constructor for RepositoryVerifierHandler.
088         *
089         * @param callingTask       A reference to the ant task using this parser.
090         */
091        public RepositoryVerifierHandler(VerifyMappingsTask callingTask)
092        {
093            m_callingTask = callingTask;
094            m_callingTask.logWarning("Loaded RepositoryVerifierHandler.");
095            logger = LoggerFactory.getLogger(this.getClass());
096        }
097    
098        /**
099         * returns the XmlCapable id associated with the literal.
100         * OJB maintains a RepositoryTags table that provides
101         * a mapping from xml-tags to XmlCapable ids.
102         *
103         * @param literal the literal to lookup
104         * @return the int value representing the XmlCapable
105         *
106         * @throws MetadataException if no literal was found in tags mapping
107         */
108        private int getLiteralId(String literal) throws PersistenceBrokerException
109        {
110            ////logger.debug("lookup: " + literal);
111            try
112            {
113                return tags.getIdByTag(literal);
114            }
115            catch (NullPointerException t)
116            {
117                throw new MetadataException("unknown literal: '" + literal + "'",t);
118            }
119    
120        }
121    
122        /**
123         * startDocument callback, nothing to do here.
124         */
125        public void startDocument()
126        {
127            //logger.debug("startDoc");
128        }
129    
130        /**
131         * endDocument callback, nothing to do here.
132         */
133        public void endDocument()
134        {
135            //logger.debug("endDoc");
136        }
137    
138    
139        public void startElement(String uri, String name, String qName, Attributes atts)
140        {
141            m_CurrentString = null;
142            try
143            {
144    
145                switch (getLiteralId(qName))
146                {
147                    case MAPPING_REPOSITORY :
148                        {
149                            String defIso = atts.getValue(tags.getTagById(ISOLATION_LEVEL));
150                            if (defIso != null)
151                            {
152                                defIsoLevel = getIsoLevel(defIso);
153                            }
154    
155                            break;
156                        }
157                    case JDBC_CONNECTION_DESCRIPTOR :
158                        {
159                            m_CurrentJCD = new JdbcConnectionDescriptor();
160    
161                            // set platform attribute
162                            String platform = atts.getValue(tags.getTagById(DBMS_NAME));
163                            //logger.debug("     " + tags.getTagById(Dbms_name) + ": " + platform);
164                            m_CurrentJCD.setDbms(platform);
165    
166                            // set jdbc-level attribute
167                            String level = atts.getValue(tags.getTagById(JDBC_LEVEL));
168                            //logger.debug("     " + tags.getTagById(Jdbc_level) + ": " + level);
169                            m_CurrentJCD.setJdbcLevel(level);
170    
171                            // set driver attribute
172                            String driver = atts.getValue(tags.getTagById(DRIVER_NAME));
173                            //logger.debug("     " + tags.getTagById(Driver_name) + ": " + driver);
174                            m_CurrentJCD.setDriver(driver);
175    
176                            // set protocol attribute
177                            String protocol = atts.getValue(tags.getTagById(URL_PROTOCOL));
178                            //logger.debug("     " + tags.getTagById(Url_protocol) + ": " + protocol);
179                            m_CurrentJCD.setProtocol(protocol);
180    
181                            // set subprotocol attribute
182                            String subprotocol = atts.getValue(tags.getTagById(URL_SUBPROTOCOL));
183                            //logger.debug("     " + tags.getTagById(Url_subprotocol) + ": " + subprotocol);
184                            m_CurrentJCD.setSubProtocol(subprotocol);
185    
186                            // set the dbalias attribute
187                            String dbalias = atts.getValue(tags.getTagById(URL_DBALIAS));
188                            //logger.debug("     " + tags.getTagById(Url_dbalias) + ": " + dbalias);
189                            m_CurrentJCD.setDbAlias(dbalias);
190    
191                            // set the datasource attribute
192                            String datasource = atts.getValue(tags.getTagById(DATASOURCE_NAME));
193                            //logger.debug("     " + tags.getTagById(Datasource_name) + ": " + datasource);
194                            m_CurrentJCD.setDatasourceName(datasource);
195    
196                            // set the user attribute
197                            String user = atts.getValue(tags.getTagById(USER_NAME));
198                            //logger.debug("     " + tags.getTagById(User_name) + ": " + user);
199                            m_CurrentJCD.setUserName(user);
200    
201                            // set the password attribute
202                            String password = atts.getValue(tags.getTagById(USER_PASSWD));
203                            //logger.debug("     " + tags.getTagById(User_passwd) + ": " + password);
204                            m_CurrentJCD.setPassWord(password);
205    
206                            //connect to the database
207                            m_dBUtility = getDBUtility(m_CurrentJCD);
208    
209                            break;
210                        }
211                    case CLASS_DESCRIPTOR :
212                        {
213                            String isoLevel = atts.getValue(tags.getTagById(ISOLATION_LEVEL));
214    
215                            // set class attribute
216                            String classname = atts.getValue(tags.getTagById(CLASS_NAME));
217    
218                            try
219                            {
220                                                            m_currentClass = m_callingTask.loadClass(classname);
221                            }
222                            catch (ClassNotFoundException ex)
223                            {
224                                //logger.error(ex);
225                                throw new MetadataException("Can't load class-descriptor class '" + classname + "'.");
226                            }
227    
228                            m_CurrentTable = atts.getValue(tags.getTagById(TABLE_NAME));
229    
230                            if (m_CurrentTable != null)
231                            {
232                                    m_currentTableExists = m_dBUtility.exists(m_CurrentTable);
233                                if(!m_currentTableExists)
234                                {
235                                    throw new MetadataException("The table '" + m_CurrentTable + "' does not exist in the database.");
236                                }
237                            }
238    
239    
240                            break;
241                        }
242    
243                    case CLASS_EXTENT :
244                        {
245    
246                            String classname = atts.getValue("class-ref");
247    
248                            try
249                            {
250                                                            Class classExtent = m_callingTask.loadClass(classname);
251                            }
252                            catch (ClassNotFoundException ex)
253                            {
254                                //logger.error(ex);
255                                throw new MetadataException("Can't load extent-class class '" + classname + "'.");
256                            }
257                            break;
258                        }
259    
260                    case FIELD_DESCRIPTOR :
261                        {
262                            String strId = atts.getValue("id");
263                            m_lastId  = (strId == null ? m_lastId + 1 : Integer.parseInt(strId));
264                            m_CurrentFLD = new FieldDescriptor(null, m_lastId);
265    
266                            String fieldName = atts.getValue(tags.getTagById(FIELD_NAME));
267    
268                            if (m_currentClass != null)
269                            {
270                                    //this.m_callingTask.logWarning("Verifying " + fieldName + " in class " + m_currentClass.toString());
271                                                            confirmFieldExists(m_currentClass, fieldName);
272    
273                                    String columnName = atts.getValue(tags.getTagById(COLUMN_NAME));
274                                    m_CurrentFLD.setColumnName(columnName);
275    
276                                    String jdbcType = atts.getValue(tags.getTagById(JDBC_TYPE));
277                                    m_CurrentFLD.setColumnType(jdbcType);
278    
279                                    //check that the field exists in the database
280                                    if(m_currentTableExists)
281                                    {
282                                            if(this.m_callingTask.getUseStrictTypeChecking())
283                                            {
284                                                                            m_dBUtility.exists(m_CurrentTable, columnName, jdbcType, m_callingTask.getIgnoreFieldNameCase());
285                                            }
286                                            else
287                                            {
288                                                                            m_dBUtility.existsUseWarnings(m_CurrentTable, columnName, jdbcType, m_callingTask.getIgnoreFieldNameCase());
289                                            }
290                                    }
291    
292                            }
293    
294                            break;
295                        }
296                    case REFERENCE_DESCRIPTOR :
297                        {
298                            if (m_currentClass != null)
299                            {
300                                    //Confirm that this field exists in the class
301                                    name = atts.getValue(tags.getTagById(FIELD_NAME));
302                                    confirmFieldExists(m_currentClass, name);
303    
304                                    String classRef = atts.getValue(tags.getTagById(REFERENCED_CLASS));
305                                    try
306                                    {
307                                            //Confirm that class 'class-ref' can be loaded.
308                                            Class refClass = m_callingTask.loadClass(classRef);
309                                }
310                                    catch (ClassNotFoundException ex)
311                                    {
312                                        //logger.error(ex);
313                                        throw new MetadataException("Can't find class-ref '" + classRef + "' in reference-descriptor '" + name + "'.");
314                                    }
315    
316                            }
317    
318                            break;
319                        }
320    
321                    case COLLECTION_DESCRIPTOR :
322                        {
323    
324                            if (m_currentClass != null)
325                            {
326                                    //Confirm that this field exists in the class
327                                    name = atts.getValue(tags.getTagById(FIELD_NAME));
328                                                            confirmFieldExists(m_currentClass, name);
329    
330                                    String collectionClass = atts.getValue(tags.getTagById(COLLECTION_CLASS));
331                                    //logger.debug("     " + tags.getTagById(Collection_class) + ": " + collectionClass);
332                                    if (collectionClass != null)
333                                    {
334                                            try
335                                            {
336                                                    //Confirm that class 'class-ref' can be loaded.
337                                                    Class oCollectionClass = m_callingTask.loadClass(collectionClass);
338                                        }
339                                            catch (ClassNotFoundException ex)
340                                            {
341                                                //logger.error(ex);
342                                                throw new MetadataException("Can't find collection-class '" + collectionClass + "' in collection-descriptor '" + name + "'.");
343                                            }
344                                    }
345                                    // set element-class-ref attribute
346                                    String elementClassRef = atts.getValue(tags.getTagById(ITEMS_CLASS));
347                                    //logger.debug("     " + tags.getTagById(Items_class) + ": " + elementClassRef);
348                                    if (elementClassRef != null)
349                                    {
350                                            try
351                                            {
352                                                    //Confirm that class 'class-ref' can be loaded.
353                                                    Class oElementClassRef = m_callingTask.loadClass(elementClassRef);
354                                        }
355                                            catch (ClassNotFoundException ex)
356                                            {
357                                                //logger.error(ex);
358                                                throw new MetadataException("Can't find element-class-ref '" + elementClassRef + "' in collection-descriptor '" + name + "'.");
359                                            }
360                                         }
361                            }
362    
363    
364                            break;
365                        }
366    
367                    default :
368                        {
369                            // nop
370                        }
371                }
372            }
373            catch(MetadataException mde)
374            {
375                    this.m_callingTask.logWarning(" --> Mapping Error: " + mde.getMessage());
376                    m_VerifyExceptions.add(mde);
377            }
378            catch(NullPointerException garbage)
379            {
380                    //eat it.
381            }
382            catch(SQLWarning sqlw)
383            {
384                    this.m_callingTask.logInfo(" --> DB Mapping Warning: " + sqlw.getMessage());
385                    m_VerifyWarnings.add(sqlw);
386            }
387            catch(SQLException sqle)
388            {
389                    this.m_callingTask.logWarning(" --> DB Mapping Error: " + sqle.getMessage());
390                    m_VerifyExceptions.add(sqle);
391            }
392            catch (Exception ex)
393            {
394                logger.error(ex);
395                throw new PersistenceBrokerException(ex);
396            }
397        }
398    
399    
400        public void endElement(String uri, String name, String qName)
401        {
402            try
403            {
404                switch (getLiteralId(qName))
405                {
406                    case MAPPING_REPOSITORY :
407                        {
408                            //release the any db connections
409                            if(m_dBUtility != null) m_dBUtility.release();
410                            break;
411                        }
412                    case JDBC_CONNECTION_DESCRIPTOR :
413                        {
414                            //logger.debug("   < " + tags.getTagById(JdbcConnectionDescriptor));
415                            break;
416                        }
417                    case CLASS_DESCRIPTOR :
418                        {
419                            //logger.debug("  < " + tags.getTagById(ClassDescriptor));
420                            m_currentClass = null;
421                                                    m_CurrentTable = null;
422                                                    m_currentTableExists = false;
423                            m_CurrentCLD = null;
424                            break;
425                        }
426                    case CLASS_EXTENT :
427                        {
428                            break;
429                        }
430    
431                    case FIELD_DESCRIPTOR :
432                        {
433                            //logger.debug("    < " + tags.getTagById(FIELDDESCRIPTOR));
434                            m_CurrentFLD = null;
435                            break;
436                        }
437                    case REFERENCE_DESCRIPTOR :
438                        {
439                            //logger.debug("    < " + tags.getTagById(ReferenceDescriptor));
440                            m_CurrentORD = null;
441                            break;
442                        }
443                    case FOREIGN_KEY :
444                        {
445                            //logger.debug("    < " + tags.getTagById(FOREIGN_KEY));
446                        }
447    
448                    case COLLECTION_DESCRIPTOR :
449                        {
450                            //logger.debug("    < " + tags.getTagById(CollectionDescriptor));
451                            m_CurrentCOD = null;
452                            break;
453                        }
454    
455                    case INVERSE_FK :
456                        {
457                            //logger.debug("    < " + tags.getTagById(Inverse_fk));
458                            break;
459                        }
460    
461                    case FK_POINTING_TO_THIS_CLASS :
462                        {
463                            //logger.debug("    < " + tags.getTagById(Fk_pointing_to_this_class));
464                            break;
465                        }
466                    case FK_POINTING_TO_ITEMS_CLASS :
467                        {
468                            //logger.debug("    < " + tags.getTagById(Fk_pointing_to_items_class));
469                            break;
470                        }
471    
472                        // handle failure:
473                    default :
474                        {
475                            //logger.error("Ignoring unknown Element " + qName);
476                        }
477                }
478            }
479            catch (Exception ex)
480            {
481                //logger.error(ex);
482                throw new PersistenceBrokerException(ex);
483            }
484        }
485    
486        public void characters(char ch[], int start, int length)
487        {
488            if (m_CurrentString == null)
489                m_CurrentString = new String(ch, start, length);
490            else
491                m_CurrentString += new String(ch, start, length);
492        }
493    
494        public void error(SAXParseException e) throws SAXException
495        {
496            //logger.error(e);
497            //release the any db connections
498            try
499            {
500                    if(m_dBUtility != null) m_dBUtility.release();
501            }
502            catch(Exception ex)
503            {
504                    ex.printStackTrace();
505            }
506            throw e;
507        }
508    
509        public void fatalError(SAXParseException e) throws SAXException
510        {
511            //logger.fatal(e);
512                    //release the any db connections
513            try
514            {
515                    if(m_dBUtility != null) m_dBUtility.release();
516            }
517            catch(Exception ex)
518            {
519                    ex.printStackTrace();
520            }
521            throw e;
522        }
523    
524        public void warning(SAXParseException e) throws SAXException
525        {
526            //logger.warn(e);
527            throw e;
528        }
529    
530        /**
531         * maps IsolationLevel literals to the corresponding id
532         * @param isoLevel
533         * @return the id
534         */
535        private int getIsoLevel(String isoLevel)
536        {
537            if (isoLevel.equalsIgnoreCase(LITERAL_IL_READ_UNCOMMITTED))
538            {
539                return IL_READ_UNCOMMITTED;
540            }
541            else if (isoLevel.equalsIgnoreCase(LITERAL_IL_READ_COMMITTED))
542            {
543                return IL_READ_COMMITTED;
544            }
545            else if (isoLevel.equalsIgnoreCase(LITERAL_IL_REPEATABLE_READ))
546            {
547                return IL_REPEATABLE_READ;
548            }
549            else if (isoLevel.equalsIgnoreCase(LITERAL_IL_SERIALIZABLE))
550            {
551                return IL_SERIALIZABLE;
552            }
553            else if (isoLevel.equalsIgnoreCase(LITERAL_IL_OPTIMISTIC))
554            {
555                return IL_OPTIMISTIC;
556            }
557            //logger.warn("unknown isolation-level: " + isoLevel + " using RW_UNCOMMITTED as default");
558            return defIsoLevel;
559        }
560    
561    
562        public int getErrorCount()
563        {
564            return m_VerifyExceptions.size();
565        }
566    
567        public int getWarningCount()
568        {
569            return m_VerifyWarnings.size();
570        }
571    
572    
573            private DBUtility getDBUtility(JdbcConnectionDescriptor jcd)
574            throws MetadataException, MalformedURLException, ClassNotFoundException
575            {
576                    DBUtility retval = null;
577                    String driver;
578                    String userName;
579                    String password;
580                    String url;
581                    //if the Tag provided the connection info use that, else
582                    //try to connect with the info in the connectionDescriptor
583                    if (m_callingTask.hasConnectionInfo())
584                    {
585                            m_callingTask.logWarning("Using DB conection info from Ant task.");
586                            driver = m_callingTask.getJdbcDriver();
587                            userName = m_callingTask.getLogon();
588                            password = m_callingTask.getPassword();
589                            url = m_callingTask.getUrl();
590                    }
591                    else
592                    {
593                            m_callingTask.logWarning("Using DB conection info from ojb repository connection descriptor.");
594                            driver = jcd.getDriver();
595                            userName = jcd.getUserName();
596                            password = jcd.getPassWord();
597                            url = jcd.getProtocol() + ":" + jcd.getSubProtocol() + ":" + jcd.getDbAlias();
598                    }
599    
600                    try
601                    {
602                        Class jdbcDriver = m_callingTask.loadClass(driver);
603    
604                        // not every jdbc driver registers itself with the driver manager
605                        // so we do it explicitly
606                        DriverManager.registerDriver((Driver)jdbcDriver.newInstance());
607                        retval = new DBUtility(url, userName, password);
608                    }
609                    catch(ClassNotFoundException cnfe)
610                    {
611                            throw cnfe;
612                    }
613                    catch (Exception e)
614                    {
615                            e.printStackTrace();
616                            throw new MetadataException("Could not connect to database with url (" +
617                            url + "), driver (" + driver + "), logon (" + userName +
618                            "), password (" + password + ").", e);
619                    }
620    
621                    return retval;
622            }
623    
624            private Constructor m_persistConstructor = null;
625            private Constructor getPersistenceClassConstructor() throws NoSuchMethodException
626            {
627                    if(m_persistConstructor == null)
628                    {
629                            //load the persistent class specified in the OJB.properties file
630                            Class persistentClass = m_callingTask.getPersistentFieldClass();
631                            Class[] aConTypes = new Class[2];
632                            aConTypes[0] = Class.class;
633                            aConTypes[1] = String.class;
634                            m_persistConstructor = persistentClass.getConstructor(aConTypes);
635                    }
636                    return m_persistConstructor;
637            }
638    
639            protected void confirmFieldExists(Class classToCheck, String fieldName)
640            throws MetadataException, NoSuchMethodException,
641                            InstantiationException, IllegalAccessException
642            {
643            Object[] aConParams = new Object[2];
644            aConParams[0] = classToCheck;
645            aConParams[1] = fieldName;
646    
647            try
648            {
649                    getPersistenceClassConstructor().newInstance(aConParams);
650            }
651            catch(InvocationTargetException ite)
652            {
653                    throw new MetadataException(ite.getTargetException().getMessage());
654            }
655            }
656    
657    }