Changeset 5477


Ignore:
Timestamp:
Nov 4, 2010, 1:40:34 PM (12 years ago)
Author:
Nicklas Nordborg
Message:

Fixes #1547: Get rid of unsafe Hibernate initialization

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/core/net/sf/basedb/core/HibernateUtil.java

    r5475 r5477  
    4747import net.sf.basedb.core.hibernate.TableExistsWork;
    4848import net.sf.basedb.core.query.QueryType;
     49import net.sf.basedb.util.FileUtil;
     50import net.sf.basedb.util.RegexpFileFilter;
    4951import net.sf.basedb.util.XMLUtil;
    5052
    5153import java.util.ArrayList;
     54import java.util.Enumeration;
    5255import java.util.LinkedList;
    5356import java.util.List;
    5457import java.util.Iterator;
    5558import java.util.Date;
     59import java.util.jar.JarEntry;
     60import java.util.jar.JarFile;
    5661import java.sql.Connection;
    5762import java.sql.Statement;
     
    6065import java.sql.SQLException;
    6166import java.net.URL;
    62 import java.net.URI;
    6367
    6468import org.jdom.Document;
     
    8488import org.hibernate.exception.ConstraintViolationException;
    8589import org.hibernate.mapping.PersistentClass;
    86 import org.hibernate.mapping.Property;
    8790import org.hibernate.mapping.Table;
    8891import org.hibernate.mapping.SimpleValue;
     
    170173    {
    171174      cfg = new Configuration();
     175      // read base.config
    172176      setConfigurationProperties(cfg);
     177      // read hibernate.cfg.xml
    173178      cfg.configure();
     179      // Get the database dialect and create a DbEngine
    174180      dialect = Dialect.getDialect(cfg.getProperties());
    175181      dbEngine = EngineFactory.createEngine(dialect);
     182      // Add database-specific functions to the configuration
    176183      addSqlFunctions(cfg, dbEngine);
    177184    }
     
    197204    {
    198205      addStaticMappings(cfg);
     206      addRawDataMappings(cfg);
    199207      mappings = cfg.createMappings();
    200       cfg.buildMappings();
    201       addExtendedPropertiesMappings(cfg, mappings);
    202       addRawDataMappings(cfg);
    203       addFilterConditions(cfg);
    204       fixOtherDbRelatedThings(cfg);
    205208      sf = cfg.buildSessionFactory();
    206209    }
     
    253256    try
    254257    {
    255       String classPath = HibernateUtil.class.getResource("HibernateUtil.class").getPath();
    256       int index = classPath.indexOf("!");
    257       if (index == -1)
     258      List<URL> mappingFiles = new ArrayList<URL>();
     259      URL coreUrl = HibernateUtil.class.getResource("/base.version");
     260      String corePath = coreUrl.getPath();
     261      if ("jar".equals(coreUrl.getProtocol()))
    258262      {
    259         // We are not in a JAR, load configuration from the directory instead
    260         cfg.addDirectory((new java.io.File(new URI(classPath).getPath())).getParentFile());
     263        // We are in a JAR file and get a path like: file:/usr/local/.../BASE2.jar!/base.version
     264        // Remove "file:" and "!/base.version" to get the path to the JAR
     265        String jarPath = corePath.substring("file:".length(), corePath.length()-"/base.version".length()-1);
     266        log.info("Loading mappings files from jar: " + jarPath);
     267        JarFile jarFile = new JarFile(jarPath);
     268        Enumeration<JarEntry> jarEntries = jarFile.entries();
     269        while (jarEntries.hasMoreElements())
     270        {
     271          JarEntry entry = jarEntries.nextElement();
     272          if (!entry.isDirectory() && entry.getName().endsWith(".hbm.xml"))
     273          {
     274            log.debug("Found mapping file: " + entry.getName());
     275            mappingFiles.add(HibernateUtil.class.getResource("/" + entry.getName()));
     276          }
     277        }
    261278      }
    262279      else
    263280      {
    264         // We get a path like: file:/usr/local/.../BASE2.jar!net/sf/basedb....
    265         // Remove everything after the "!" to get the path
    266         // to this JAR, which contains all XML mapping files.
    267         cfg.addJar(new java.io.File(new URI(classPath.substring(0,index)).getPath()));
     281        // We are in a directory and get a path like: /usr/local/.../base.version
     282        // Move up one directory to get to the root path
     283        java.io.File dir = new java.io.File(corePath).getParentFile();
     284        log.info("Loading mappings files from directory: " + dir);
     285        List<java.io.File> files = FileUtil.findFiles(dir, new RegexpFileFilter(".*\\.hbm\\.xml", null));
     286        for (java.io.File hbmFile : files)
     287        {
     288          log.debug("Found mapping file: " + hbmFile);
     289          mappingFiles.add(hbmFile.toURI().toURL());
     290        }
    268291      }
     292     
     293      // Load the XML mapping files and register them with Hibernate
     294      final URL dtdFile = HibernateUtil.class.getResource("/org/hibernate/hibernate-mapping-3.0.dtd");
     295      DOMOutputter out = new DOMOutputter();
     296      for (URL url : mappingFiles)
     297      {
     298        log.info("Loading mapping file: " + url);
     299        Document dom = XMLUtil.getValidatedXml(url, dtdFile);
     300        manipulateDocument(dom);
     301        cfg.addDocument(out.output(dom));
     302      }
    269303    }
    270304    catch (Exception ex)
    271305    {
    272       log.error("Exception while adding Static Mappings", ex);
    273       throw new BaseException(ex);
    274     }
    275   }
    276 
    277   /**
    278     Generate additional mappings for all {@link ExtendableData} items.
    279   */
    280   private static void addExtendedPropertiesMappings(Configuration cfg, Mappings mappings)
    281   {
    282     List<String> classes = ExtendedProperties.getClasses();
    283     String packageName = BasicData.class.getPackage().getName();
    284     for (String className : classes)
    285     {
    286       PersistentClass pc = cfg.getClassMapping(packageName+"."+className);
    287       if (pc == null)
     306      log.error("Exception while loading mapping files", ex);
     307      throw new BaseException(ex);
     308    }
     309  }
     310
     311  /**
     312    Some XML files require manipulation by us before they are registered.
     313    <ul>
     314    <li>Add extended properties to extendable classes
     315    <li>Add filter implementation to almost all classes
     316    <li>Other special-case fixes depending on the underlying database
     317    </ul>
     318   
     319  */
     320  private static void manipulateDocument(Document dom)
     321  {
     322    // Get the <class> tag
     323    Element classTag = dom.getRootElement().getChild("class");
     324    String className = classTag != null ? classTag.getAttributeValue("name") : null;
     325    // We are only interested in <hibernate-mapping> with <class> elements
     326    if (className == null) return;
     327   
     328    // Extended properties
     329    if (ExtendedProperties.isExtendable(className))
     330    {
     331      addExtendedProperties(classTag, className);
     332    }
     333   
     334    // Permission filters
     335    addFilterConditions(classTag, className);
     336   
     337    // Unit symbol need to be case-sensitive
     338    if (className.endsWith("UnitSymbolData"))
     339    {
     340      fixUnitSymbolColumnDefinition(classTag);
     341    }
     342  }
     343 
     344  /**
     345    Append &lt;property&gt; tags to the given class tag.
     346    @param classTag An Element representing a &lt;class&gt; tag
     347    @param className The name of the ({@link ExtendableData}) class
     348    @since 2.17
     349  */
     350  private static void addExtendedProperties(Element classTag, String className)
     351  {
     352    log.info("Adding extended properties to class: " + className);
     353    List<ExtendedProperty> properties = ExtendedProperties.getProperties(className);
     354
     355    for (ExtendedProperty property : properties)
     356    {
     357      log.debug("Adding extended property to class '" + className + "': " + property.getName());
     358      classTag.addContent(createPropertyElement(property));
     359    }
     360  }
     361 
     362  /**
     363    Creates a &lt;property&gt; tag from an extended property
     364    definition. This also works for raw data properties since they
     365    extend from ExtendedProperty.
     366    @since 2.17
     367  */
     368  private static Element createPropertyElement(ExtendedProperty property)
     369  {
     370    Element element = new Element("property");
     371    element.setAttribute("name", property.getName());
     372    element.setAttribute("column", "`"+property.getColumn()+"`");
     373    element.setAttribute("type", property.getType().getStringValue());
     374    element.setAttribute("length", Integer.toString(property.getLength()));
     375    element.setAttribute("not-null", Boolean.toString(!property.isNullable()));
     376    element.setAttribute("access", "net.sf.basedb.core.ExtendedPropertyAccessor");
     377    return element;
     378  }
     379 
     380  /**
     381    Add &lt;filter&gt; conditions to the given class tag.
     382    @param classTag An Element representing a &lt;class&gt; tag
     383    @param className The name of the entity class
     384    @since 2.17
     385  */
     386  private static void addFilterConditions(Element classTag, String className)
     387  {
     388    try
     389    {
     390      Class c = Class.forName(className);
     391      log.info("Adding 'denyAll' filter to "+c.getName());
     392      classTag.addContent(createFilterElement("denyAll", "`id` != `id`"));
     393      if (NewsData.class.isAssignableFrom(c))
    288394      {
    289         throw new BaseException("Class " + packageName+"."+className + " is not known to Hibernate.");
     395        log.info("Adding 'todaysNews' filter to "+c.getName());
     396        classTag.addContent(createFilterElement("todaysNews",
     397            ":today >= `start_date` AND (:today <= `end_date` OR `end_date` IS NULL)"));
    290398      }
    291       List<ExtendedProperty> properties = ExtendedProperties.getProperties(className);
    292       for (ExtendedProperty ep : properties)
     399      if (MessageData.class.isAssignableFrom(c))
    293400      {
    294         log.info("Adding extended property " + ep.getName() + " to class " + className);
    295         pc.addProperty(createExtendedProperty(mappings, pc.getTable(), ep, true));
     401        log.info("Adding 'ownedBy' filter to " + c.getName());
     402        classTag.addContent(createFilterElement("ownedBy", ":owner = `to_user_id`"));
    296403      }
    297     }
    298   }
    299 
    300 
     404      if (RemovableData.class.isAssignableFrom(c))
     405      {
     406        log.info("Adding 'isRemoved' filter to "+c.getName());
     407        classTag.addContent(createFilterElement("isRemoved", ":removed = `removed`"));
     408      }
     409      if (AnnotatableData.class.isAssignableFrom(c))
     410      {
     411        log.info("Adding 'isAnnotatated' filter to "+c.getName());
     412        classTag.addContent(createFilterElement("isAnnotated", ":annotated = (`annotationset_id` IS NOT NULL)"));
     413      }
     414      if (OwnableData.class.isAssignableFrom(c))
     415      {
     416        log.info("Adding 'ownedBy' filter to "+c.getName());
     417        classTag.addContent(createFilterElement("ownedBy", ":owner = `owner`"));
     418       
     419        log.info("Adding 'notOwnedBy' filter to "+c.getName());
     420        classTag.addContent(createFilterElement("notOwnedBy", ":owner != `owner`"));
     421 
     422        log.info("Adding 'memberOf' filter to "+c.getName());
     423        classTag.addContent(createFilterElement("memberOf", ":owner != `owner` AND `id` IN (:items)"));
     424 
     425        log.info("Adding 'ownedByOrMemberOf' filter to "+c.getName());
     426        classTag.addContent(createFilterElement("ownedByOrMemberOf", "(:owner = `owner` OR `id` IN (:items))"));
     427      }
     428      else if (UserData.class.isAssignableFrom(c))
     429      {
     430        log.info("Adding 'memberOf' filter to "+c.getName());
     431        classTag.addContent(createFilterElement("memberOf", "(`id` IN (:items) OR `id` = :owner)"));
     432      }
     433      else
     434      {
     435        log.info("Adding 'memberOf' filter to "+c.getName());
     436        classTag.addContent(createFilterElement("memberOf", "`id` IN (:items)"));
     437      }
     438      if (ShareableData.class.isAssignableFrom(c))
     439      {
     440        log.info("Adding 'sharedTo' filter to "+c.getName());
     441        classTag.addContent(createFilterElement("sharedTo", ":owner != `owner` AND `itemkey_id` IN (:itemKeys)"));
     442 
     443        log.info("Adding 'inProject' filter to "+c.getName());
     444        classTag.addContent(createFilterElement("inProject", "`projectkey_id` IN (:projectKeys)"));
     445 
     446        log.info("Adding 'ownedByOrSharedTo' filter to "+c.getName());
     447        classTag.addContent(createFilterElement("ownedByOrSharedTo", "(:owner = `owner` OR `itemkey_id` IN (:itemKeys))"));
     448 
     449        log.info("Adding 'ownedByOrInProject' filter to "+c.getName());
     450        classTag.addContent(createFilterElement("ownedByOrInProject",
     451            "(:owner = `owner` OR `projectkey_id` IN (:projectKeys))"));
     452 
     453        log.info("Adding 'sharedToOrInProject' filter to "+c.getName());
     454        classTag.addContent(createFilterElement("sharedToOrInProject",
     455            "((:owner != `owner` AND `itemkey_id` IN (:itemKeys)) OR `projectkey_id` IN (:projectKeys))"));
     456 
     457        log.info("Adding 'ownedByOrSharedToOrInProject' filter to "+c.getName());
     458        classTag.addContent(createFilterElement("ownedByOrSharedToOrInProject",
     459            "(:owner = `owner` OR `itemkey_id` IN (:itemKeys) OR `projectkey_id` IN (:projectKeys))"));
     460      }
     461    }
     462    catch (ClassNotFoundException ex)
     463    {
     464      throw new RuntimeException(ex);
     465    }
     466  }
     467   
     468  /**
     469    Create a &lt;filter&gt; tag using the given name and condition.
     470    @since 2.17
     471  */
     472  private static Element createFilterElement(String name, String condition)
     473  {
     474    Element element = new Element("filter");
     475    element.setAttribute("name", name);
     476    element.setAttribute("condition", condition);
     477    return element;
     478  }
     479 
    301480  /**
    302481    Generate additional mappings for the raw data types.
     
    309488    final URL templateFile = HibernateUtil.class.getResource("/net/sf/basedb/core/templates/hibernate-properties-RawData.xml");
    310489
     490    DOMOutputter out = new DOMOutputter();
    311491    for (RawDataType rdt : RawDataTypes.getRawDataTypes() )
    312492    {
     
    315495        String entityName = rdt.getEntityName();
    316496        String tableName = rdt.getTableName();
    317         log.info("Adding raw data type " + entityName);
     497        log.info("Adding raw data type: " + entityName);
    318498        try
    319499        {
     
    334514          for (RawDataProperty property : rdt.getProperties())
    335515          {
    336             log.info("Adding raw data property " + property.getName() + " to class " + entityName);
    337             Element el = new Element("property");
    338             el.setAttribute("name", property.getName());
    339             el.setAttribute("column", "`"+property.getColumn()+"`");
    340             el.setAttribute("type", property.getType().getStringValue());
    341             el.setAttribute("length", Integer.toString(property.getLength()));
    342             el.setAttribute("not-null", Boolean.toString(!property.isNullable()));
    343             el.setAttribute("access", "net.sf.basedb.core.ExtendedPropertyAccessor");
    344             clazz.addContent(el);
     516            log.info("Adding raw data property to class '" + entityName + "': " + property.getName());
     517            clazz.addContent(createPropertyElement(property));
    345518          }
    346           cfg.addDocument(new DOMOutputter().output(dom));
     519          cfg.addDocument(out.output(dom));
    347520        }
    348521        catch (Exception ex)
    349522        {
    350           log.error("Exception while adding raw data types", ex);
     523          log.error("Exception while adding raw data type: " + entityName, ex);
    351524          throw new BaseException(ex);
    352525        }
     
    355528    }
    356529  }
    357   /**
    358     Add an extended property to the specified Hibernate mapping
    359     @param t The table the property should be added to
    360     @param ep The extended property to add to the mapping
    361     @param extendable TRUE if the property belongs to an extendable item, FALSE
    362       otherwise (RawData)
    363   */
    364   private static Property createExtendedProperty(Mappings mappings, Table t, ExtendedProperty ep, boolean extendable)
    365   {
    366     org.hibernate.mapping.Column c = new org.hibernate.mapping.Column();
    367     c.setName("`"+ep.getColumn()+"`");
    368     c.setNullable(ep.isNullable());
    369     c.setLength(ep.getLength());
    370 
    371     t.addColumn(c);
    372 
    373     SimpleValue v = new SimpleValue(mappings, t);
    374     v.addColumn(c);
    375     v.setTypeName(ep.getType().getTypeWrapper().getHibernateType().getName());
    376 
    377     Property p = new Property();
    378     p.setValue(v);
    379     p.setName(ep.getName());
    380     p.setNodeName(ep.getName());
    381     if (extendable) p.setPropertyAccessorName("net.sf.basedb.core.ExtendedPropertyAccessor");
    382     p.setCascade("none");
    383     p.setUpdateable(ep.isUpdateable());
    384     p.setInsertable(ep.isInsertable());
    385     return p;
    386   }
    387 
    388   /**
    389     Generate filters for {@link Ownable}, {@link Shareable}, etc. classes
    390     that are used by the {@link Query} implementations. We are generating the filters
    391     here beacuse we don't want anyone to mess them which could have been the case
    392     if the filter had been defined in the xml mapping files.
    393   */
    394   private static void addFilterConditions(Configuration cfg)
    395     throws BaseException
    396   {
    397     assert cfg != null : "cfg == null";
    398     Iterator<PersistentClass> iterator = cfg.getClassMappings();
    399     while (iterator.hasNext())
    400     {
    401       PersistentClass pc = iterator.next();
    402       Class c = pc.getMappedClass();
    403       log.info("Adding 'denyAll' filter to "+c.getName());
    404       pc.addFilter("denyAll", "`id` != `id`");
    405       if (NewsData.class.isAssignableFrom(c))
    406       {
    407         log.info("Adding 'todaysNews' filter to "+c.getName());
    408         pc.addFilter("todaysNews", ":today >= `start_date` AND (:today <= `end_date` OR `end_date` IS NULL)");
    409       }
    410       if (MessageData.class.isAssignableFrom(c))
    411       {
    412         log.info("Adding 'ownedBy' filter to " + c.getName());
    413         pc.addFilter("ownedBy", ":owner = `to_user_id`");
    414       }
    415       if (RemovableData.class.isAssignableFrom(c))
    416       {
    417         log.info("Adding 'isRemoved' filter to "+c.getName());
    418         pc.addFilter("isRemoved", ":removed = `removed`");
    419       }
    420       if (AnnotatableData.class.isAssignableFrom(c))
    421       {
    422         log.info("Adding 'isAnnotatated' filter to "+c.getName());
    423         pc.addFilter("isAnnotated", ":annotated = (`annotationset_id` IS NOT NULL)");
    424       }
    425       if (OwnableData.class.isAssignableFrom(c))
    426       {
    427         log.info("Adding 'ownedBy' filter to "+c.getName());
    428         pc.addFilter("ownedBy", ":owner = `owner`");
    429        
    430         log.info("Adding 'notOwnedBy' filter to "+c.getName());
    431         pc.addFilter("notOwnedBy", ":owner != `owner`");
    432 
    433         log.info("Adding 'memberOf' filter to "+c.getName());
    434         pc.addFilter("memberOf", ":owner != `owner` AND `id` IN (:items)");
    435 
    436         log.info("Adding 'ownedByOrMemberOf' filter to "+c.getName());
    437         pc.addFilter("ownedByOrMemberOf", "(:owner = `owner` OR `id` IN (:items))");
    438       }
    439       else if (UserData.class.isAssignableFrom(c))
    440       {
    441         log.info("Adding 'memberOf' filter to "+c.getName());
    442         pc.addFilter("memberOf", "(`id` IN (:items) OR `id` = :owner)");
    443       }
    444       else
    445       {
    446         log.info("Adding 'memberOf' filter to "+c.getName());
    447         pc.addFilter("memberOf", "`id` IN (:items)");
    448       }
    449       if (ShareableData.class.isAssignableFrom(c))
    450       {
    451         log.info("Adding 'sharedTo' filter to "+c.getName());
    452         pc.addFilter("sharedTo", ":owner != `owner` AND `itemkey_id` IN (:itemKeys)");
    453 
    454         log.info("Adding 'inProject' filter to "+c.getName());
    455         pc.addFilter("inProject", "`projectkey_id` IN (:projectKeys)");
    456 
    457         log.info("Adding 'ownedByOrSharedTo' filter to "+c.getName());
    458         pc.addFilter("ownedByOrSharedTo", "(:owner = `owner` OR `itemkey_id` IN (:itemKeys))");
    459 
    460         log.info("Adding 'ownedByOrInProject' filter to "+c.getName());
    461         pc.addFilter("ownedByOrInProject", "(:owner = `owner` OR `projectkey_id` IN (:projectKeys))");
    462 
    463         log.info("Adding 'sharedToOrInProject' filter to "+c.getName());
    464         pc.addFilter("sharedToOrInProject", "((:owner != `owner` AND `itemkey_id` IN (:itemKeys)) OR `projectkey_id` IN (:projectKeys))");
    465 
    466         log.info("Adding 'ownedByOrSharedToOrInProject' filter to "+c.getName());
    467         pc.addFilter("ownedByOrSharedToOrInProject", "(:owner = `owner` OR `itemkey_id` IN (:itemKeys) OR `projectkey_id` IN (:projectKeys))");
    468       }
    469     }
    470   }
    471  
     530
    472531  /**
    473532    Add more SQL functions to HQL that are not present in a
     
    494553 
    495554  /**
    496     Last step in setting up the configuration. Use this method to fix
    497     things that require special handling.
    498     @since 2.9
    499   */
    500   private static void fixOtherDbRelatedThings(Configuration cfg)
     555    Fixes the definition of the UnitSymbols.symbol column so that
     556    it is case-sensitive. We need because some units have both
     557    milli (m) and Mega (M) as prefix (eg. mV and MV)
     558    @since 2.17
     559  */
     560  private static void fixUnitSymbolColumnDefinition(Element classTag)
    501561  {
    502562    // Make the UnitSymbols.symbol column case-sensitive so we can have units
    503563    // with both milli (m) and Mega (M) as prefix.
    504     PersistentClass unitSymbols = cfg.getClassMapping("net.sf.basedb.core.data.UnitSymbolData");
    505     Column symbol = (Column)unitSymbols.getProperty("symbol").getColumnIterator().next();
    506     if (dbEngine.caseInsensitiveComparison())
    507     {
    508       String sqlType = dbEngine.getCaseSensitiveVarchar(symbol.getLength());
    509       if (sqlType != null) symbol.setSqlType(sqlType);
     564    if (!dbEngine.caseInsensitiveComparison()) return;
     565    log.debug("Setting UnitSymbols.symbol to a case-sensitive column type");
     566   
     567    // Find the class/property/column[name=`symbol`] tag
     568    Iterator match = classTag.getDescendants(
     569      new org.jdom.filter.Filter()
     570      {
     571        private static final long serialVersionUID = -2876632679035736428L;
     572        @Override
     573        public boolean matches(Object obj)
     574        {
     575          if (!(obj instanceof Element)) return false;
     576 
     577          Element e = (Element)obj;
     578          return e.getName().equals("column") && e.getAttributeValue("name").equals("`symbol`");
     579        }
     580      });
     581    Element col = (Element)match.next();
     582    String sqlType = dbEngine.getCaseSensitiveVarchar(XMLUtil.getIntAttribute(col, "length", 255));
     583    if (sqlType != null)
     584    {
     585      col.setAttribute("sql-type", sqlType);
    510586    }
    511587  }
Note: See TracChangeset for help on using the changeset viewer.