001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.orm.hibernate;
020
021import java.io.BufferedReader;
022import java.io.IOException;
023import java.nio.charset.Charset;
024import java.sql.SQLException;
025import java.sql.Statement;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Map;
032import java.util.Map.Entry;
033import java.util.Set;
034
035import lucee.commons.io.log.Log;
036import lucee.commons.io.log.LogUtil;
037import lucee.commons.io.res.Resource;
038import lucee.commons.io.res.filter.ExtensionResourceFilter;
039import lucee.commons.io.res.util.ResourceUtil;
040import lucee.commons.lang.StringUtil;
041import lucee.commons.sql.SQLUtil;
042import lucee.loader.util.Util;
043import lucee.runtime.Component;
044import lucee.runtime.InterfacePage;
045import lucee.runtime.Mapping;
046import lucee.runtime.Page;
047import lucee.runtime.PageContext;
048import lucee.runtime.PageSource;
049import lucee.runtime.component.ComponentLoader;
050import lucee.runtime.config.Config;
051import lucee.runtime.config.Constants;
052import lucee.runtime.db.DataSource;
053import lucee.runtime.db.DatasourceConnection;
054import lucee.runtime.exp.PageException;
055import lucee.runtime.listener.ApplicationContext;
056import lucee.runtime.orm.ORMConfiguration;
057import lucee.runtime.reflection.Reflector;
058import lucee.runtime.type.Collection.Key;
059
060import org.hibernate.MappingException;
061import org.hibernate.cache.RegionFactory;
062import org.hibernate.cfg.Configuration;
063import org.hibernate.tool.hbm2ddl.SchemaExport;
064import org.hibernate.tool.hbm2ddl.SchemaUpdate;
065import org.w3c.dom.Document;
066
067
068public class HibernateSessionFactory {
069        
070        
071        public static final String HIBERNATE_3_PUBLIC_ID = "-//Hibernate/Hibernate Mapping DTD 3.0//EN";
072        public static final String HIBERNATE_3_SYSTEM_ID = "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd";
073        public static final Charset HIBERNATE_3_CHARSET = CommonUtil.UTF8;
074        public static final String HIBERNATE_3_DOCTYPE_DEFINITION = "<!DOCTYPE hibernate-mapping PUBLIC \""+HIBERNATE_3_PUBLIC_ID+"\" \""+HIBERNATE_3_SYSTEM_ID+"\">";
075        
076
077        public static Configuration createConfiguration(Log log,String mappings, DatasourceConnection dc, SessionFactoryData data) throws SQLException, IOException, PageException {
078                /*
079                 autogenmap
080                 cacheconfig
081                 cacheprovider
082                 cfclocation
083                 datasource
084                 dbcreate
085                 eventHandling
086                 flushatrequestend
087                 ormconfig
088                 sqlscript
089                 useDBForMapping
090                 */ 
091                
092                ORMConfiguration ormConf = data.getORMConfiguration();
093                
094                // dialect
095                DataSource ds = dc.getDatasource();
096                String dialect=null;
097                try     {
098                        if (Class.forName(ormConf.getDialect()) != null) {
099                                dialect = ormConf.getDialect();
100                        }
101                }
102                catch (Exception e) {
103                        // MZ: The dialect value could not be bound to a classname or instantiation causes an exception - ignore and use the default dialect entries
104                }
105                if (dialect == null) {
106                        dialect = Dialect.getDialect(ormConf.getDialect());
107                        if(Util.isEmpty(dialect)) dialect=Dialect.getDialect(ds);
108                }
109                if(Util.isEmpty(dialect))
110                        throw ExceptionUtil.createException(data,null,"A valid dialect definition inside the "+Constants.APP_CFC+"/"+Constants.CFAPP_NAME+" is missing. The dialect cannot be determinated automatically",null);
111                
112                // Cache Provider
113                String cacheProvider = ormConf.getCacheProvider();
114                Class<? extends RegionFactory> regionFactory=null;
115                
116                if(Util.isEmpty(cacheProvider) || "EHCache".equalsIgnoreCase(cacheProvider)) {
117                        //regionFactory=net.sf.ehcache.hibernate.EhCacheRegionFactory.class;
118                        regionFactory=net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory.class;
119                        cacheProvider=regionFactory.getName();//"org.hibernate.cache.EhCacheProvider";
120                                        
121                }
122                else if("JBossCache".equalsIgnoreCase(cacheProvider))   cacheProvider="org.hibernate.cache.TreeCacheProvider";
123                else if("HashTable".equalsIgnoreCase(cacheProvider))    cacheProvider="org.hibernate.cache.HashtableCacheProvider";
124                else if("SwarmCache".equalsIgnoreCase(cacheProvider))   cacheProvider="org.hibernate.cache.SwarmCacheProvider";
125                else if("OSCache".equalsIgnoreCase(cacheProvider))              cacheProvider="org.hibernate.cache.OSCacheProvider";
126        
127                Resource cacheConfig = ormConf.getCacheConfig();
128                Configuration configuration = new Configuration();
129                
130                // ormConfig
131                Resource conf = ormConf.getOrmConfig();
132                if(conf!=null){
133                        try {
134                                Document doc = CommonUtil.toDocument(conf,null);
135                                configuration.configure(doc);
136                        } 
137                        catch (Throwable t) {
138                                lucee.commons.lang.ExceptionUtil.rethrowIfNecessary(t);
139                                LogUtil.log(log, Log.LEVEL_ERROR, "hibernate", t);
140                                
141                        }
142                }
143                
144                try{
145                        configuration.addXML(mappings);
146                }
147                catch(MappingException me){
148                        throw ExceptionUtil.createException(data,null, me);
149                }
150                
151                configuration
152        
153        // Database connection settings
154        .setProperty("hibernate.connection.driver_class", ds.getClazz().getName())
155        .setProperty("hibernate.connection.url", ds.getDsnTranslated());
156                if(!StringUtil.isEmpty(ds.getUsername())) {
157                        configuration.setProperty("hibernate.connection.username", ds.getUsername());
158                        if(!StringUtil.isEmpty(ds.getPassword()))
159                                configuration.setProperty("hibernate.connection.password", ds.getPassword());
160                }
161        //.setProperty("hibernate.connection.release_mode", "after_transaction")
162        configuration.setProperty("hibernate.transaction.flush_before_completion", "false")
163        .setProperty("hibernate.transaction.auto_close_session", "false")
164        
165        // JDBC connection pool (use the built-in)
166        //.setProperty("hibernate.connection.pool_size", "2")//MUST
167        
168        
169        // SQL dialect
170        .setProperty("hibernate.dialect", dialect)
171        // Enable Hibernate's current session context
172        .setProperty("hibernate.current_session_context_class", "thread")
173        
174        // Echo all executed SQL to stdout
175        .setProperty("hibernate.show_sql", CommonUtil.toString(ormConf.logSQL()))
176        .setProperty("hibernate.format_sql", CommonUtil.toString(ormConf.logSQL()))
177        // Specifies whether secondary caching should be enabled
178        .setProperty("hibernate.cache.use_second_level_cache", CommonUtil.toString(ormConf.secondaryCacheEnabled()))
179                // Drop and re-create the database schema on startup
180        .setProperty("hibernate.exposeTransactionAwareSessionFactory", "false")
181                //.setProperty("hibernate.hbm2ddl.auto", "create")
182                .setProperty("hibernate.default_entity_mode", "dynamic-map");
183                
184                if(!Util.isEmpty(ormConf.getCatalog()))
185                        configuration.setProperty("hibernate.default_catalog", ormConf.getCatalog());
186                if(!Util.isEmpty(ormConf.getSchema()))
187                        configuration.setProperty("hibernate.default_schema",ormConf.getSchema());
188                
189                
190                if(ormConf.secondaryCacheEnabled()){
191                        if(cacheConfig!=null && cacheConfig.isFile())
192                                configuration.setProperty("hibernate.cache.provider_configuration_file_resource_path",cacheConfig.getAbsolutePath());
193                        if(regionFactory!=null || Reflector.isInstaneOf(cacheProvider, RegionFactory.class))
194                                configuration.setProperty("hibernate.cache.region.factory_class", cacheProvider);
195                        else
196                                configuration.setProperty("hibernate.cache.provider_class", cacheProvider);
197                        
198                        configuration.setProperty("hibernate.cache.use_query_cache", "true");
199                
200                //hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider
201                }
202                
203                /*
204                <!ELEMENT tuplizer EMPTY> 
205            <!ATTLIST tuplizer entity-mode (pojo|dom4j|dynamic-map) #IMPLIED>   <!-- entity mode for which tuplizer is in effect --> 
206            <!ATTLIST tuplizer class CDATA #REQUIRED>                           <!-- the tuplizer class to use --> 
207                */
208        
209                schemaExport(log,configuration,dc,data);
210                
211                return configuration;
212        }
213
214        private static void schemaExport(Log log,Configuration configuration, DatasourceConnection dc, SessionFactoryData data) throws PageException, SQLException, IOException {
215                ORMConfiguration ormConf = data.getORMConfiguration();
216                
217                if(ORMConfiguration.DBCREATE_NONE==ormConf.getDbCreate()) {
218                        return;
219                }
220                else if(ORMConfiguration.DBCREATE_DROP_CREATE==ormConf.getDbCreate()) {
221                        SchemaExport export = new SchemaExport(configuration);
222                        export.setHaltOnError(true);
223                    
224                        export.execute(false,true,false,false);
225            printError(log,data,export.getExceptions(),false);
226            executeSQLScript(ormConf,dc);
227                }
228                else if(ORMConfiguration.DBCREATE_UPDATE==ormConf.getDbCreate()) {
229                        SchemaUpdate update = new SchemaUpdate(configuration);
230            update.setHaltOnError(true);
231            update.execute(false, true);
232            printError(log,data,update.getExceptions(),false);
233        }
234        }
235
236        private static void printError(Log log,SessionFactoryData data, List<Exception> exceptions,boolean throwException) throws PageException {
237                if(exceptions==null || exceptions.size()==0) return;
238                Iterator<Exception> it = exceptions.iterator();
239        if(!throwException || exceptions.size()>1){
240                        while(it.hasNext()) {
241                                LogUtil.log(log, Log.LEVEL_ERROR, "hibernate", it.next());
242                } 
243        }
244        if(!throwException) return;
245        
246        it = exceptions.iterator();
247        while(it.hasNext()) {
248                throw ExceptionUtil.createException(data,null,it.next());
249        } 
250        }
251
252        private static void executeSQLScript(ORMConfiguration ormConf,DatasourceConnection dc) throws SQLException, IOException {
253        Resource sqlScript = ormConf.getSqlScript();
254        if(sqlScript!=null && sqlScript.isFile()) {
255                BufferedReader br = CommonUtil.toBufferedReader(sqlScript,(Charset)null);
256                String line;
257            StringBuilder sql=new StringBuilder();
258            String str;
259            Statement stat = dc.getConnection().createStatement();
260                try{
261                        while((line=br.readLine())!=null){
262                        line=line.trim();
263                        if(line.startsWith("//") || line.startsWith("--")) continue;
264                        if(line.endsWith(";")){
265                                sql.append(line.substring(0,line.length()-1));
266                                str=sql.toString().trim();
267                                if(str.length()>0)stat.execute(str);
268                                sql=new StringBuilder();
269                        }
270                        else {
271                                sql.append(line).append(" ");
272                        }       
273                    }
274                        str=sql.toString().trim();
275                        if(str.length()>0){
276                                stat.execute(str);
277                    }
278                }
279                finally {
280                        SQLUtil.closeEL(stat);
281                }
282        }
283    }
284
285
286        public static Map<Key,String> createMappings(ORMConfiguration ormConf, SessionFactoryData data) {
287                Map<Key,String> mappings=new HashMap<Key,String>();
288                Iterator<Entry<Key, Map<String, CFCInfo>>> it = data.getCFCs().entrySet().iterator();
289                while(it.hasNext()){
290                        Entry<Key, Map<String, CFCInfo>> e = it.next();
291                        
292                        Set<String> done=new HashSet<String>();
293                        StringBuilder mapping=new StringBuilder();
294                        mapping.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
295                        mapping.append(HIBERNATE_3_DOCTYPE_DEFINITION+"\n");
296                        mapping.append("<hibernate-mapping>\n");
297                        Iterator<Entry<String, CFCInfo>> _it = e.getValue().entrySet().iterator();
298                        Entry<String, CFCInfo> entry;
299                        while(_it.hasNext()){
300                                entry = _it.next();
301                                createMappings(ormConf,entry.getKey(),entry.getValue(),done,mapping,data);
302                                
303                        }
304                        mapping.append("</hibernate-mapping>");
305                        mappings.put(e.getKey(), mapping.toString());
306                }
307                return mappings;
308        }
309
310        private static void createMappings(ORMConfiguration ormConf, String key, CFCInfo value,Set<String> done,StringBuilder mappings, SessionFactoryData data) {
311                if(done.contains(key)) return;
312                CFCInfo v;
313                String ext = value.getCFC().getExtends();
314                if(!Util.isEmpty(ext)){
315                        try {
316                                Component base = data.getEntityByCFCName(ext, false);
317                                ext=HibernateCaster.getEntityName(base);
318                        } catch (Throwable t) {
319                                lucee.commons.lang.ExceptionUtil.rethrowIfNecessary(t);
320                        }
321                        
322                        
323                        ext=HibernateUtil.id(CommonUtil.last(ext, '.').trim());
324                        if(!done.contains(ext)) {
325                                v = data.getCFC(ext,null);
326                                if(v!=null)createMappings(ormConf, ext, v, done, mappings,data);
327                        }
328                }
329                
330                mappings.append(value.getXML());
331                done.add(key);
332        }
333
334        public static List<Component> loadComponents(PageContext pc,HibernateORMEngine engine, ORMConfiguration ormConf) throws PageException {
335                ExtensionResourceFilter filter = new ExtensionResourceFilter(pc.getConfig().getCFCExtension(),true);
336                List<Component> components=new ArrayList<Component>();
337                loadComponents(pc,engine,components,ormConf.getCfcLocations(),filter,ormConf);
338                return components;
339        }
340        
341        private static void loadComponents(PageContext pc, HibernateORMEngine engine,List<Component> components,Resource[] reses,ExtensionResourceFilter filter,ORMConfiguration ormConf) throws PageException {
342                Mapping[] mappings = createMappings(pc, reses);
343                ApplicationContext ac=pc.getApplicationContext();
344                Mapping[] existing = ac.getComponentMappings();
345                if(existing==null) existing=new Mapping[0];
346                try{
347                        Mapping[] tmp = new Mapping[existing.length+1];
348                        for(int i=1;i<tmp.length;i++){
349                                tmp[i]=existing[i-1];
350                        }
351                        ac.setComponentMappings(tmp);
352                        for(int i=0;i<reses.length;i++){
353                                if(reses[i]!=null && reses[i].isDirectory()){
354                                        tmp[0] = mappings[i];
355                                        ac.setComponentMappings(tmp);
356                                        loadComponents(pc,engine,mappings[i],components,reses[i], filter,ormConf);
357                                }
358                        }
359                }
360                finally {
361                        ac.setComponentMappings(existing);
362                }
363        }
364        
365        private static void loadComponents(PageContext pc, HibernateORMEngine engine,Mapping cfclocation,List<Component> components,Resource res,ExtensionResourceFilter filter,ORMConfiguration ormConf) throws PageException {
366                if(res==null) return;
367
368                if(res.isDirectory()){
369                        Resource[] children = res.listResources(filter);
370                        
371                        // first load all files
372                        for(int i=0;i<children.length;i++){
373                                if(children[i].isFile())loadComponents(pc,engine,cfclocation,components,children[i], filter,ormConf);
374                        }
375                        
376                        // and then invoke subfiles
377                        for(int i=0;i<children.length;i++){
378                                if(children[i].isDirectory())loadComponents(pc,engine,cfclocation,components,children[i], filter,ormConf);
379                        }
380                }
381                else if(res.isFile()){
382                        if(!res.getName().equalsIgnoreCase(Constants.APP_CFC))  {
383                                try {
384                                        
385                                        // MUST still a bad solution
386                                        PageSource ps = pc.toPageSource(res,null);
387                                        if(ps==null || ps.getComponentName().indexOf("..")!=-1) {
388                                                PageSource ps2=null;
389                                                Resource root = cfclocation.getPhysical();
390                                String path = ResourceUtil.getPathToChild(res, root);
391                                if(!Util.isEmpty(path,true)) {
392                                        ps2=cfclocation.getPageSource(path);
393                                }
394                                if(ps2!=null)ps=ps2;
395                                        }
396                                        
397                                        
398                                        //Page p = ps.loadPage(pc.getConfig());
399                                        String name=res.getName();
400                                        name=name.substring(0,name.length()-4);
401                                        Page p = ComponentLoader.loadPage(pc, ps,true);
402                                        if(!(p instanceof InterfacePage)){
403                                                Component cfc = ComponentLoader.loadComponent(pc, p, ps, name, true,true);
404                                                if(CommonUtil.isPersistent(cfc)){
405                                                        components.add(cfc);
406                                                }
407                                        }
408                                } 
409                                catch (PageException e) {
410                                        if(!ormConf.skipCFCWithError())throw e;
411                                        //e.printStackTrace();
412                                }
413                        }
414                }
415        }
416
417        
418        public static Mapping[] createMappings(PageContext pc,Resource[] resources) {
419                        
420                        Mapping[] mappings=new Mapping[resources.length];
421                        Config config=pc.getConfig();
422                        for(int i=0;i<mappings.length;i++) {
423                                mappings[i]=CommonUtil.createMapping(config,
424                                                "/",
425                                                resources[i].getAbsolutePath()
426                                                );
427                        }
428                        return mappings;
429                }
430}