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.util.ArrayList;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Map.Entry;
027import java.util.concurrent.ConcurrentHashMap;
028
029import lucee.commons.io.log.Log;
030import lucee.commons.io.res.Resource;
031import lucee.loader.util.Util;
032import lucee.runtime.Component;
033import lucee.runtime.PageContext;
034import lucee.runtime.config.ConfigImpl;
035import lucee.runtime.db.DataSource;
036import lucee.runtime.db.DataSourceManager;
037import lucee.runtime.db.DatasourceConnection;
038import lucee.runtime.exp.PageException;
039import lucee.runtime.listener.ApplicationContext;
040import lucee.runtime.listener.ApplicationContextPro;
041import lucee.runtime.orm.ORMConfiguration;
042import lucee.runtime.orm.ORMEngine;
043import lucee.runtime.orm.ORMSession;
044import lucee.runtime.orm.ORMUtil;
045import lucee.runtime.orm.hibernate.event.AllEventListener;
046import lucee.runtime.orm.hibernate.event.EventListener;
047import lucee.runtime.orm.hibernate.event.InterceptorImpl;
048import lucee.runtime.orm.hibernate.event.PostDeleteEventListenerImpl;
049import lucee.runtime.orm.hibernate.event.PostInsertEventListenerImpl;
050import lucee.runtime.orm.hibernate.event.PostLoadEventListenerImpl;
051import lucee.runtime.orm.hibernate.event.PostUpdateEventListenerImpl;
052import lucee.runtime.orm.hibernate.event.PreDeleteEventListenerImpl;
053import lucee.runtime.orm.hibernate.event.PreInsertEventListenerImpl;
054import lucee.runtime.orm.hibernate.event.PreLoadEventListenerImpl;
055import lucee.runtime.orm.hibernate.event.PreUpdateEventListenerImpl;
056import lucee.runtime.orm.hibernate.tuplizer.AbstractEntityTuplizerImpl;
057import lucee.runtime.text.xml.XMLCaster;
058import lucee.runtime.type.Collection;
059import lucee.runtime.type.Collection.Key;
060import lucee.runtime.type.util.ListUtil;
061
062import org.hibernate.EntityMode;
063import org.hibernate.SessionFactory;
064import org.hibernate.cfg.Configuration;
065import org.hibernate.event.EventListeners;
066import org.hibernate.event.PostDeleteEventListener;
067import org.hibernate.event.PostInsertEventListener;
068import org.hibernate.event.PostLoadEventListener;
069import org.hibernate.event.PostUpdateEventListener;
070import org.hibernate.event.PreDeleteEventListener;
071import org.hibernate.event.PreLoadEventListener;
072import org.hibernate.tuple.entity.EntityTuplizerFactory;
073import org.w3c.dom.Document;
074import org.w3c.dom.Element;
075
076public class HibernateORMEngine implements ORMEngine {
077        
078        private static final int INIT_NOTHING=1;
079        private static final int INIT_CFCS=2;
080        private static final int INIT_ALL=2;
081
082        private Map<String,SessionFactoryData> factories=new ConcurrentHashMap<String, SessionFactoryData>();
083        
084        public HibernateORMEngine() {}
085
086        @Override
087        public void init(PageContext pc) throws PageException{
088                SessionFactoryData data = getSessionFactoryData(pc, INIT_CFCS);
089                data.init();// init all factories
090        }
091                
092        @Override
093        public ORMSession createSession(PageContext pc) throws PageException {
094                try{
095                        SessionFactoryData data = getSessionFactoryData(pc, INIT_NOTHING);
096                        return new HibernateORMSession(pc,data);
097                }
098                catch(PageException pe){
099                        throw pe;
100                }
101        }
102        
103
104        /*QueryPlanCache getQueryPlanCache(PageContext pc) throws PageException {
105                return getSessionFactoryData(pc,INIT_NOTHING).getQueryPlanCache();
106        }*/
107
108        /*public SessionFactory getSessionFactory(PageContext pc) throws PageException{
109                return getSessionFactory(pc,INIT_NOTHING);
110        }*/
111        
112        public boolean reload(PageContext pc, boolean force) throws PageException {
113                if(force) {
114                        getSessionFactoryData(pc, INIT_ALL);
115                }
116                else {
117                        if(factories.containsKey(hash(pc)))return false;
118                }
119                getSessionFactoryData(pc, INIT_CFCS);
120                return true;
121        }
122
123        private SessionFactoryData getSessionFactoryData(PageContext pc,int initType) throws PageException {
124                ApplicationContextPro appContext = (ApplicationContextPro) pc.getApplicationContext();
125                if(!appContext.isORMEnabled())
126                        throw ExceptionUtil.createException((ORMSession)null,null,"ORM is not enabled","");
127                
128                
129                // datasource
130                ORMConfiguration ormConf=appContext.getORMConfiguration();
131                String key = hash(ormConf);
132                SessionFactoryData data = factories.get(key);
133                if(initType==INIT_ALL && data!=null) {
134                        data.reset();
135                        data=null;
136                }
137                if(data==null) {
138                        data=new SessionFactoryData(this,ormConf);
139                        factories.put(key, data);
140                }
141                
142                
143                // config
144                try{
145                        //arr=null;
146                        if(initType!=INIT_NOTHING){
147                                synchronized (data) {
148                                        
149                                        if(ormConf.autogenmap()){
150                                                data.tmpList=HibernateSessionFactory.loadComponents(pc, this, ormConf);
151                                                
152                                                data.clearCFCs();
153                                        }
154                                        else 
155                                                throw ExceptionUtil.createException(data,null,"orm setting autogenmap=false is not supported yet",null);
156                                
157                                        // load entities
158                                        if(data.tmpList!=null && data.tmpList.size()>0) {
159                                                data.getNamingStrategy();// called here to make sure, it is called in the right context the first one
160                                                
161                                                // creates CFCInfo objects
162                                                {
163                                                        Iterator<Component> it = data.tmpList.iterator();
164                                                        while(it.hasNext()){
165                                                                createMapping(pc,it.next(),ormConf,data);
166                                                        }
167                                                }
168                                                
169                                                if(data.tmpList.size()!=data.sizeCFCs()){
170                                                        Component cfc;
171                                                        String name,lcName;
172                                                        Map<String,String> names=new HashMap<String,String>();
173                                                        Iterator<Component> it = data.tmpList.iterator();
174                                                        while(it.hasNext()){
175                                                                cfc=it.next();
176                                                                name=HibernateCaster.getEntityName(cfc);
177                                                                lcName=name.toLowerCase();
178                                                                if(names.containsKey(lcName))
179                                                                        throw ExceptionUtil.createException(data,null,"Entity Name ["+name+"] is ambigous, ["+names.get(lcName)+"] and ["+cfc.getPageSource().getDisplayPath()+"] use the same entity name.",""); 
180                                                                names.put(lcName,cfc.getPageSource().getDisplayPath());
181                                                        }       
182                                                }
183                                        }
184                                }
185                        }
186                }
187                finally {
188                        data.tmpList=null;
189                }
190                                
191                // already initialized for this application context
192                
193                //MUST
194                //cacheconfig
195                //cacheprovider
196                //...
197                
198                Log log = ((ConfigImpl)pc.getConfig()).getLog("orm");
199                
200                Iterator<Entry<Key, String>> it = HibernateSessionFactory.createMappings(ormConf,data).entrySet().iterator();
201                Entry<Key, String> e;
202                while(it.hasNext()) {
203                        e = it.next();
204                        if(data.getConfiguration(e.getKey())!=null) continue;
205                        
206                        DatasourceConnection dc = CommonUtil.getDatasourceConnection(pc,data.getDataSource(e.getKey()));
207                        try{
208                                data.setConfiguration(log,e.getValue(),dc);
209                        } 
210                        catch (Exception ex) {
211                                throw CommonUtil.toPageException(ex);
212                        }
213                        finally {
214                                CommonUtil.releaseDatasourceConnection(pc, dc);
215                        }
216                        addEventListeners(pc, data,e.getKey());
217                        
218                        EntityTuplizerFactory tuplizerFactory = data.getConfiguration(e.getKey()).getEntityTuplizerFactory();
219                        tuplizerFactory.registerDefaultTuplizerClass(EntityMode.MAP, AbstractEntityTuplizerImpl.class);
220                        tuplizerFactory.registerDefaultTuplizerClass(EntityMode.POJO, AbstractEntityTuplizerImpl.class);
221                        
222                        data.buildSessionFactory(e.getKey());
223                }
224                
225                return data;
226        }
227        
228        private static void addEventListeners(PageContext pc, SessionFactoryData data, Key key) throws PageException {
229                if(!data.getORMConfiguration().eventHandling()) return;
230                String eventHandler = data.getORMConfiguration().eventHandler();
231                AllEventListener listener=null;
232                if(!Util.isEmpty(eventHandler,true)){
233                        //try {
234                                Component c = pc.loadComponent(eventHandler.trim());
235                                
236                                listener = new AllEventListener(c);
237                        //config.setInterceptor(listener);
238                        //}catch (PageException e) {e.printStackTrace();}
239                }
240                Configuration conf = data.getConfiguration(key);
241                conf.setInterceptor(new InterceptorImpl(listener));
242        EventListeners listeners = conf.getEventListeners();
243        Map<String, CFCInfo> cfcs = data.getCFCs(key);
244        // post delete
245                List<EventListener> 
246                list=merge(listener,cfcs,CommonUtil.POST_DELETE);
247                listeners.setPostDeleteEventListeners(list.toArray(new PostDeleteEventListener[list.size()]));
248                
249        // post insert
250                list=merge(listener,cfcs,CommonUtil.POST_INSERT);
251                listeners.setPostInsertEventListeners(list.toArray(new PostInsertEventListener[list.size()]));
252                
253                // post update
254                list=merge(listener,cfcs,CommonUtil.POST_UPDATE);
255                listeners.setPostUpdateEventListeners(list.toArray(new PostUpdateEventListener[list.size()]));
256                
257                // post load
258                list=merge(listener,cfcs,CommonUtil.POST_LOAD);
259                listeners.setPostLoadEventListeners(list.toArray(new PostLoadEventListener[list.size()]));
260                
261                // pre delete
262                list=merge(listener,cfcs,CommonUtil.PRE_DELETE);
263                listeners.setPreDeleteEventListeners(list.toArray(new PreDeleteEventListener[list.size()]));
264                
265                // pre insert
266                //list=merge(listener,cfcs,CommonUtil.PRE_INSERT);
267                //listeners.setPreInsertEventListeners(list.toArray(new PreInsertEventListener[list.size()]));
268                
269                // pre load
270                list=merge(listener,cfcs,CommonUtil.PRE_LOAD);
271                listeners.setPreLoadEventListeners(list.toArray(new PreLoadEventListener[list.size()]));
272                
273                // pre update
274                //list=merge(listener,cfcs,CommonUtil.PRE_UPDATE);
275                //listeners.setPreUpdateEventListeners(list.toArray(new PreUpdateEventListener[list.size()]));
276        }
277
278        private static List<EventListener> merge(EventListener listener, Map<String, CFCInfo> cfcs, Collection.Key eventType) {
279                List<EventListener> list=new ArrayList<EventListener>();
280                        
281                
282                Iterator<Entry<String, CFCInfo>> it = cfcs.entrySet().iterator();
283                Entry<String, CFCInfo> entry;
284                Component cfc;
285                while(it.hasNext()){
286                        entry = it.next();
287                        cfc = entry.getValue().getCFC();
288                        if(EventListener.hasEventType(cfc,eventType)) {
289                                if(CommonUtil.POST_DELETE.equals(eventType))
290                                        list.add(new PostDeleteEventListenerImpl(cfc));
291                                if(CommonUtil.POST_INSERT.equals(eventType))
292                                        list.add(new PostInsertEventListenerImpl(cfc));
293                                if(CommonUtil.POST_LOAD.equals(eventType))
294                                        list.add(new PostLoadEventListenerImpl(cfc));
295                                if(CommonUtil.POST_UPDATE.equals(eventType))
296                                        list.add(new PostUpdateEventListenerImpl(cfc));
297                                
298                                if(CommonUtil.PRE_DELETE.equals(eventType))
299                                        list.add(new PreDeleteEventListenerImpl(cfc));
300                                if(CommonUtil.PRE_INSERT.equals(eventType))
301                                        list.add(new PreInsertEventListenerImpl(cfc));
302                                if(CommonUtil.PRE_LOAD.equals(eventType))
303                                        list.add(new PreLoadEventListenerImpl(cfc));
304                                if(CommonUtil.PRE_UPDATE.equals(eventType))
305                                        list.add(new PreUpdateEventListenerImpl(cfc));
306                        }
307                }
308                
309                // general listener
310                if(listener!=null && EventListener.hasEventType(listener.getCFC(),eventType))
311                        list.add(listener);
312                
313                return list;
314        }
315
316        private static Object hash(PageContext pc) {
317                ApplicationContextPro appContext=(ApplicationContextPro) pc.getApplicationContext();
318                return hash(appContext.getORMConfiguration());
319        }
320        
321        private static String hash(ORMConfiguration ormConf) {
322                return ormConf.hash();
323        }
324
325        public void createMapping(PageContext pc,Component cfc, ORMConfiguration ormConf,SessionFactoryData data) throws PageException {
326                String entityName=HibernateCaster.getEntityName(cfc);
327                CFCInfo info=data.getCFC(entityName,null);
328                String xml;
329                long cfcCompTime = HibernateUtil.getCompileTime(pc,cfc.getPageSource());
330                if(info==null || (ORMUtil.equals(info.getCFC(),cfc) ))  {//&& info.getModified()!=cfcCompTime
331                        DataSource ds = ORMUtil.getDataSource(pc,cfc);
332                        StringBuilder sb=new StringBuilder();
333                        
334                        long xmlLastMod = loadMapping(sb,ormConf, cfc);
335                        Element root;
336                        // create mapping
337                        if(true || xmlLastMod< cfcCompTime) {//MUSTMUST
338                                data.reset();
339                                Document doc=null;
340                                try {
341                                        doc=CommonUtil.newDocument();
342                                }catch(Throwable t){
343                                        lucee.commons.lang.ExceptionUtil.rethrowIfNecessary(t);
344                                        t.printStackTrace();
345                                }
346                                
347                                root=doc.createElement("hibernate-mapping");
348                                doc.appendChild(root);
349                                pc.addPageSource(cfc.getPageSource(), true);
350                                DataSourceManager manager = pc.getDataSourceManager();
351                                DatasourceConnection dc = manager.getConnection(pc, ds, null, null);
352                                try{
353                                        HBMCreator.createXMLMapping(pc,dc,cfc,root,data);
354                                }
355                                finally{
356                                        pc.removeLastPageSource(true);
357                                        manager.releaseConnection(pc, dc);
358                                }
359                                xml=XMLCaster.toString(root.getChildNodes(),true,true);
360                                saveMapping(ormConf,cfc,root);
361                        }
362                        // load
363                        else {
364                                xml=sb.toString();
365                                root=CommonUtil.toXML(xml).getOwnerDocument().getDocumentElement();
366                                /*print.o("1+++++++++++++++++++++++++++++++++++++++++");
367                                print.o(xml);
368                                print.o("2+++++++++++++++++++++++++++++++++++++++++");
369                                print.o(root);
370                                print.o("3+++++++++++++++++++++++++++++++++++++++++");*/
371                                
372                        }
373                        data.addCFC(entityName,new CFCInfo(HibernateUtil.getCompileTime(pc,cfc.getPageSource()),xml,cfc,ds));
374                }
375                
376        }
377
378        private static void saveMapping(ORMConfiguration ormConf, Component cfc, Element hm) {
379                if(ormConf.saveMapping()){
380                        Resource res=cfc.getPageSource().getResource();
381                        if(res!=null){
382                                res=res.getParentResource().getRealResource(res.getName()+".hbm.xml");
383                                try{
384                                CommonUtil.write(res, 
385                                                XMLCaster.toString(hm,false,true,
386                                                                HibernateSessionFactory.HIBERNATE_3_PUBLIC_ID,
387                                                                HibernateSessionFactory.HIBERNATE_3_SYSTEM_ID,
388                                                                HibernateSessionFactory.HIBERNATE_3_CHARSET.name()), HibernateSessionFactory.HIBERNATE_3_CHARSET, false);
389                                }
390                                catch(Exception e){} 
391                        }
392                }
393        }
394        
395        private static long loadMapping(StringBuilder sb,ORMConfiguration ormConf, Component cfc) {
396                
397                Resource res=cfc.getPageSource().getResource();
398                if(res!=null){
399                        res=res.getParentResource().getRealResource(res.getName()+".hbm.xml");
400                        try{
401                                sb.append(CommonUtil.toString(res, CommonUtil.UTF8));
402                                return res.lastModified();
403                        }
404                        catch(Exception e){} 
405                }
406                return 0;
407        }
408
409        @Override
410        public int getMode() {
411                //MUST impl
412                return MODE_LAZY;
413        }
414
415        @Override
416        public String getLabel() {
417                return "Hibernate";
418        }
419
420        
421        
422        
423
424        @Override
425        public ORMConfiguration getConfiguration(PageContext pc) {
426                ApplicationContext ac = pc.getApplicationContext();
427                if(!ac.isORMEnabled())
428                        return null;
429                return  ac.getORMConfiguration();
430        }
431
432        /**
433         * @param pc
434         * @param session
435         * @param entityName name of the entity to get
436         * @param unique create a unique version that can be manipulated
437         * @param init call the nit method of the cfc or not
438         * @return
439         * @throws PageException
440         */
441        public Component create(PageContext pc, HibernateORMSession session,String entityName, boolean unique) throws PageException {
442                SessionFactoryData data = session.getSessionFactoryData();
443                // get existing entity
444                Component cfc = _create(pc,entityName,unique,data);
445                if(cfc!=null)return cfc;
446                
447                SessionFactoryData oldData = getSessionFactoryData(pc, INIT_NOTHING);
448                Map<Key, SessionFactory> oldFactories = oldData.getFactories();
449                SessionFactoryData newData = getSessionFactoryData(pc, INIT_CFCS);
450                Map<Key, SessionFactory> newFactories = newData.getFactories();
451                
452                Iterator<Entry<Key, SessionFactory>> it = oldFactories.entrySet().iterator();
453                Entry<Key, SessionFactory> e;
454                SessionFactory newSF;
455                while(it.hasNext()){
456                        e = it.next();
457                        newSF = newFactories.get(e.getKey());
458                        if(e.getValue()!=newSF){
459                                session.resetSession(pc,newSF,e.getKey(),oldData);
460                                cfc = _create(pc,entityName,unique,data);
461                                if(cfc!=null)return cfc;
462                        }
463                }
464                
465                
466                
467                ORMConfiguration ormConf = pc.getApplicationContext().getORMConfiguration();
468                Resource[] locations = ormConf.getCfcLocations();
469                
470                throw ExceptionUtil.createException(data,null,
471                                "No entity (persitent component) with name ["+entityName+"] found, available entities are ["+ListUtil.listToList(data.getEntityNames(), ", ")+"] ",
472                                "component are searched in the following directories ["+toString(locations)+"]");
473                
474        }
475        
476        
477        private String toString(Resource[] locations) {
478                if(locations==null) return "";
479                StringBuilder sb=new StringBuilder();
480                for(int i=0;i<locations.length;i++){
481                        if(i>0) sb.append(", ");
482                        sb.append(locations[i].getAbsolutePath());
483                }
484                return sb.toString();
485        }
486
487        private static Component _create(PageContext pc, String entityName, boolean unique, SessionFactoryData data) throws PageException {
488                CFCInfo info = data.getCFC(entityName, null);
489                if(info!=null) {
490                        Component cfc = info.getCFC();
491                        if(unique){
492                                cfc=(Component)cfc.duplicate(false);
493                                if(cfc.contains(pc,CommonUtil.INIT))cfc.call(pc, "init",new Object[]{});
494                        }
495                        return cfc;
496                }
497                return null;
498        }
499}
500class CFCInfo {
501        private String xml;
502        private long modified;
503        private Component cfc;
504        private DataSource ds;
505        
506        public CFCInfo(long modified, String xml, Component cfc, DataSource ds) {
507                this.modified=modified;
508                this.xml=xml;
509                this.cfc=cfc;
510                this.ds=ds;
511        }
512        /**
513         * @return the cfc
514         */
515        public Component getCFC() {
516                return cfc;
517        }
518        /**
519         * @return the xml
520         */
521        public String getXML() {
522                return xml;
523        }
524        /**
525         * @return the modified
526         */
527        public long getModified() {
528                return modified;
529        }
530        
531        public DataSource getDataSource() {
532                return ds;
533        }
534        
535}
536