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;
020
021import java.util.ArrayList;
022import java.util.HashSet;
023import java.util.Iterator;
024import java.util.Map.Entry;
025
026import lucee.commons.io.SystemUtil;
027import lucee.commons.lang.StringUtil;
028import lucee.commons.lang.SystemOut;
029import lucee.runtime.Component;
030import lucee.runtime.PageContext;
031import lucee.runtime.PageContextImpl;
032import lucee.runtime.component.Property;
033import lucee.runtime.config.ConfigImpl;
034import lucee.runtime.config.Constants;
035import lucee.runtime.db.DataSource;
036import lucee.runtime.engine.ThreadLocalPageContext;
037import lucee.runtime.exp.PageException;
038import lucee.runtime.listener.ApplicationContextPro;
039import lucee.runtime.op.Caster;
040import lucee.runtime.op.Decision;
041import lucee.runtime.op.Operator;
042import lucee.runtime.orm.hibernate.ExceptionUtil;
043import lucee.runtime.type.Collection;
044import lucee.runtime.type.Collection.Key;
045import lucee.runtime.type.KeyImpl;
046import lucee.runtime.type.Struct;
047import lucee.runtime.type.StructImpl;
048import lucee.runtime.type.util.ComponentProUtil;
049import lucee.runtime.type.util.KeyConstants;
050import lucee.runtime.type.util.ListUtil;
051
052public class ORMUtil {
053
054        public static ORMSession getSession(PageContext pc) throws PageException {
055                return getSession(pc,true);
056        }
057        
058        public static ORMSession getSession(PageContext pc, boolean create) throws PageException {
059                return ((PageContextImpl) pc).getORMSession(create);
060        }
061
062        public static ORMEngine getEngine(PageContext pc) throws PageException {
063                ConfigImpl config=(ConfigImpl) pc.getConfig();
064                return config.getORMEngine(pc);
065        }
066
067        /**
068         * 
069         * @param pc
070         * @param force if set to false the engine is on loaded when the configuration has changed
071         * @throws PageException
072         */
073        public static void resetEngine(PageContext pc, boolean force) throws PageException {
074                ConfigImpl config=(ConfigImpl) pc.getConfig();
075                config.resetORMEngine(pc,force);
076        }
077        
078        public static void printError(Throwable t, ORMEngine engine) {
079                printError(t, engine, t.getMessage());
080        }
081
082        public static void printError(String msg, ORMEngine engine) {
083                printError(null, engine, msg);
084        }
085        
086        public static void printError(Throwable t) {
087                printError(t, null, t.getMessage());
088        }
089
090        public static void printError(String msg) {
091                printError(null, null, msg);
092        }
093
094        private static void printError(Throwable t, ORMEngine engine,String msg) {
095                if(engine!=null)SystemOut.printDate("{"+engine.getLabel().toUpperCase()+"} - "+msg,SystemUtil.ERR);
096                else SystemOut.printDate(msg, SystemUtil.ERR);
097                if(t==null)t=new Throwable();
098                t.printStackTrace(SystemOut.getPrinWriter(SystemUtil.ERR));
099        }
100
101        public static boolean equals(Object left, Object right) {
102                HashSet<Object> done=new HashSet<Object>();
103                return _equals(done, left, right);
104        }
105        
106        private static boolean _equals(HashSet<Object> done,Object left, Object right) {
107                
108                if(left==right) return true;
109                if(left==null || right==null) return false;
110                
111                // components
112                if(left instanceof Component && right instanceof Component){
113                        return _equals(done,(Component)left, (Component)right);
114                }
115
116                // arrays
117                if(Decision.isArray(left) && Decision.isArray(right)){
118                        return _equals(done,Caster.toArray(left,null), Caster.toArray(right,null));
119                }
120
121                // struct
122                if(Decision.isStruct(left) && Decision.isStruct(right)){
123                        return _equals(done,Caster.toStruct(left,null), Caster.toStruct(right,null));
124                }
125                
126                try {
127                        return Operator.equals(left,right,false);
128                } catch (PageException e) {
129                        return false;
130                }
131        }
132        
133        private static boolean _equals(HashSet<Object> done,Collection left, Collection right) {
134                if(done.contains(left)) return done.contains(right);
135                done.add(left);
136                done.add(right);
137                
138                if(left.size()!=right.size()) return false;
139                //Key[] keys = left.keys();
140                Iterator<Entry<Key, Object>> it = left.entryIterator();
141                Entry<Key, Object> e;
142                Object l,r;
143                while(it.hasNext()){
144                        e = it.next();
145                        l=e.getValue();
146                        r=right.get(e.getKey(),null);
147                        if(r==null || !_equals(done,l, r)) return false;
148                }
149                return true;
150        }
151        
152        private static boolean _equals(HashSet<Object> done,Component left, Component right) {
153                if(done.contains(left)) return done.contains(right);
154                done.add(left);
155                done.add(right);
156                
157                if(left==null || right==null) return false;
158                if(!left.getPageSource().equals(right.getPageSource())) return false;
159                Property[] props = getProperties(left);
160                Object l,r;
161                props=getIds(props);
162                for(int i=0;i<props.length;i++){
163                        l=left.getComponentScope().get(KeyImpl.init(props[i].getName()),null);
164                        r=right.getComponentScope().get(KeyImpl.init(props[i].getName()),null);
165                        if(!_equals(done,l, r)) return false;
166                }
167                return true;
168        }
169        
170        public static Property[] getIds(Property[] props) {
171                ArrayList<Property> ids=new ArrayList<Property>();
172        for(int y=0;y<props.length;y++){
173                String fieldType = Caster.toString(props[y].getDynamicAttributes().get(KeyConstants._fieldtype,null),null);
174                        if("id".equalsIgnoreCase(fieldType) || ListUtil.listFindNoCaseIgnoreEmpty(fieldType,"id",',')!=-1)
175                                ids.add(props[y]);
176                }
177        
178        // no id field defined
179        if(ids.size()==0) {
180                String fieldType;
181                for(int y=0;y<props.length;y++){
182                        fieldType = Caster.toString(props[y].getDynamicAttributes().get(KeyConstants._fieldtype,null),null);
183                        if(StringUtil.isEmpty(fieldType,true) && props[y].getName().equalsIgnoreCase("id")){
184                                ids.add(props[y]);
185                                props[y].getDynamicAttributes().setEL(KeyConstants._fieldtype, "id");
186                        }
187                }
188        } 
189        
190        // still no id field defined
191        if(ids.size()==0 && props.length>0) {
192                String owner = props[0].getOwnerName();
193                        if(!StringUtil.isEmpty(owner)) owner=ListUtil.last(owner, '.').trim();
194                
195                String fieldType;
196                if(!StringUtil.isEmpty(owner)){
197                        String id=owner+"id";
198                        for(int y=0;y<props.length;y++){
199                                fieldType = Caster.toString(props[y].getDynamicAttributes().get(KeyConstants._fieldtype,null),null);
200                                if(StringUtil.isEmpty(fieldType,true) && props[y].getName().equalsIgnoreCase(id)){
201                                        ids.add(props[y]);
202                                        props[y].getDynamicAttributes().setEL(KeyConstants._fieldtype, "id");
203                                }
204                        }
205                }
206        } 
207        return ids.toArray(new Property[ids.size()]);
208        }
209        
210        public static Object getPropertyValue(Component cfc, String name, Object defaultValue) {
211                Property[] props=getProperties(cfc);
212                
213                for(int i=0;i<props.length;i++){
214                        if(!props[i].getName().equalsIgnoreCase(name)) continue;
215                        return cfc.getComponentScope().get(KeyImpl.getInstance(name),null);
216                }
217                return defaultValue;
218        }
219        /* jira2049
220        public static Object getPropertyValue(ORMSession session,Component cfc, String name, Object defaultValue) {
221                Property[] props=getProperties(cfc);
222                Object raw=null;
223                SessionImpl sess=null;
224                if(session!=null){
225                        raw=session.getRawSession();
226                        if(raw instanceof SessionImpl)
227                                sess=(SessionImpl) raw;
228                }
229                Object val;
230                for(int i=0;i<props.length;i++){
231                        if(!props[i].getName().equalsIgnoreCase(name)) continue;
232                        val = cfc.getComponentScope().get(KeyImpl.getInstance(name),null);
233                        if(sess!=null && !(val instanceof PersistentCollection)){
234                                if(val instanceof List)
235                                        return new PersistentList(sess,(List)val);
236                                if(val instanceof Map && !(val instanceof Component))
237                                        return new PersistentMap(sess,(Map)val);
238                                if(val instanceof Set)
239                                        return new PersistentSet(sess,(Set)val);
240                                if(val instanceof Array)
241                                        return new PersistentList(sess,Caster.toList(val,null));
242                                        
243                        }
244                        return val;
245                }
246                return defaultValue;
247        }*/
248
249        private static Property[] getProperties(Component cfc) {
250                return ComponentProUtil.getProperties(cfc, true,true,false,false);
251        }
252        
253        public static boolean isRelated(Property prop) {
254                String fieldType = Caster.toString(prop.getDynamicAttributes().get(KeyConstants._fieldtype,"column"),"column");
255                if(StringUtil.isEmpty(fieldType,true)) return false;
256                fieldType=fieldType.toLowerCase().trim();
257                
258                if("one-to-one".equals(fieldType))              return true;
259                if("many-to-one".equals(fieldType))     return true;
260                if("one-to-many".equals(fieldType))     return true;
261                if("many-to-many".equals(fieldType))    return true;
262                return false;
263        }
264        
265        public static Struct convertToSimpleMap(String paramsStr) {
266                paramsStr=paramsStr.trim();
267        if(!StringUtil.startsWith(paramsStr, '{') || !StringUtil.endsWith(paramsStr, '}'))
268                return null;
269                
270                paramsStr = paramsStr.substring(1, paramsStr.length() - 1);
271                String items[] = ListUtil.listToStringArray(paramsStr, ','); 
272                
273                Struct params=new StructImpl();
274                String arr$[] = items;
275                int index;
276        for(int i = 0; i < arr$.length; i++) {
277            String pair = arr$[i];
278            index = pair.indexOf('=');
279            if(index == -1) return null;
280            
281            params.setEL(
282                        KeyImpl.init(deleteQuotes(pair.substring(0, index).trim()).trim()), 
283                        deleteQuotes(pair.substring(index + 1).trim()));
284        }
285
286        return params;
287    }
288        
289        private static String deleteQuotes(String str)  {
290        if(StringUtil.isEmpty(str,true))return "";
291        char first=str.charAt(0);
292        if((first=='\'' || first=='"') && StringUtil.endsWith(str, first))
293                return str.substring(1, str.length() - 1);
294        return str;
295    }
296        
297        public static DataSource getDefaultDataSource(PageContext pc) throws PageException{
298                pc=ThreadLocalPageContext.get(pc);
299                Object o=((ApplicationContextPro)pc.getApplicationContext()).getORMDataSource();
300                
301                if(StringUtil.isEmpty(o))
302                        throw ExceptionUtil.createException((ORMSession)null/* no session here, otherwise we get a infiniti loop*/,null,"missing datasource defintion in "+Constants.APP_CFC+"/"+Constants.CFAPP_NAME,null);
303                return o instanceof DataSource?(DataSource)o:((PageContextImpl)pc).getDataSource(Caster.toString(o));
304        }
305        
306        public static DataSource getDefaultDataSource(PageContext pc, DataSource defaultValue) {
307                pc=ThreadLocalPageContext.get(pc);
308                Object o=((ApplicationContextPro)pc.getApplicationContext()).getORMDataSource();
309                if(StringUtil.isEmpty(o))
310                        return defaultValue;
311                try {
312                        return o instanceof DataSource?(DataSource)o:((PageContextImpl)pc).getDataSource(Caster.toString(o));
313                }
314                catch (PageException e) {
315                        return defaultValue;
316                }
317        }
318
319        public static DataSource getDataSource(PageContext pc, String dsn, DataSource defaultValue) {
320                if(StringUtil.isEmpty(dsn,true)) return ORMUtil.getDefaultDataSource(pc,defaultValue);
321                return ((PageContextImpl)pc).getDataSource(dsn.trim(),defaultValue);
322        }
323        public static DataSource getDataSource(PageContext pc, String dsn) throws PageException {
324                if(StringUtil.isEmpty(dsn,true)) return ORMUtil.getDefaultDataSource(pc);
325                return ((PageContextImpl)pc).getDataSource(dsn.trim());
326        }
327        
328        
329        /**
330         * if the given component has defined a datasource in the meta data, lucee is returning this datasource, 
331         * otherwise the default orm datasource is returned
332         * @param pc
333         * @param cfc
334         * @return
335         * @throws PageException
336         */
337        public static DataSource getDataSource(PageContext pc, Component cfc, DataSource defaultValue) {
338                pc=ThreadLocalPageContext.get(pc);
339                
340                // datasource defined with cfc
341                try{
342                        Struct meta = cfc.getMetaData(pc);
343                        String datasourceName = Caster.toString(meta.get(KeyConstants._datasource,null),null);
344                        if(!StringUtil.isEmpty(datasourceName,true)) {
345                                DataSource ds = ((PageContextImpl)pc).getDataSource(datasourceName,null);
346                                if(ds!=null) return ds;
347                        }
348                }
349                catch(Throwable t){
350                        lucee.commons.lang.ExceptionUtil.rethrowIfNecessary(t);
351                }
352                return getDefaultDataSource(pc, defaultValue);
353        }
354        
355        /**
356         * if the given component has defined a datasource in the meta data, lucee is returning this datasource, 
357         * otherwise the default orm datasource is returned
358         * @param pc
359         * @param cfc
360         * @return
361         * @throws PageException
362         */
363        public static DataSource getDataSource(PageContext pc, Component cfc) throws PageException {
364                pc=ThreadLocalPageContext.get(pc);
365                
366                // datasource defined with cfc
367                Struct meta = cfc.getMetaData(pc);
368                String datasourceName = Caster.toString(meta.get(KeyConstants._datasource,null),null);
369                if(!StringUtil.isEmpty(datasourceName,true)) {
370                        return ((PageContextImpl)pc).getDataSource(datasourceName);
371                }
372                
373                return getDefaultDataSource(pc);
374        }
375        public static String getDataSourceName(PageContext pc, Component cfc) throws PageException {
376                pc=ThreadLocalPageContext.get(pc);
377                
378                // datasource defined with cfc
379                Struct meta = cfc.getMetaData(pc);
380                String datasourceName = Caster.toString(meta.get(KeyConstants._datasource,null),null);
381                if(!StringUtil.isEmpty(datasourceName,true)) {
382                        return datasourceName.trim();
383                }
384                return getDefaultDataSource(pc).getName();
385        }
386}