001    package railo.runtime.type.scope.storage;
002    
003    import java.sql.Connection;
004    import java.sql.PreparedStatement;
005    import java.sql.SQLException;
006    import java.sql.Types;
007    
008    import railo.commons.io.log.Log;
009    import railo.runtime.PageContext;
010    import railo.runtime.config.Config;
011    import railo.runtime.config.ConfigImpl;
012    import railo.runtime.converter.ConverterException;
013    import railo.runtime.converter.ScriptConverter;
014    import railo.runtime.db.DataSourceImpl;
015    import railo.runtime.db.DatasourceConnection;
016    import railo.runtime.db.DatasourceConnectionPool;
017    import railo.runtime.db.SQL;
018    import railo.runtime.db.SQLCaster;
019    import railo.runtime.db.SQLImpl;
020    import railo.runtime.db.SQLItem;
021    import railo.runtime.db.SQLItemImpl;
022    import railo.runtime.debug.DebuggerImpl;
023    import railo.runtime.exp.ApplicationException;
024    import railo.runtime.exp.DatabaseException;
025    import railo.runtime.exp.PageException;
026    import railo.runtime.op.Caster;
027    import railo.runtime.type.Collection;
028    import railo.runtime.type.KeyImpl;
029    import railo.runtime.type.Query;
030    import railo.runtime.type.QueryImpl;
031    import railo.runtime.type.Struct;
032    import railo.runtime.type.dt.DateTime;
033    import railo.runtime.type.dt.DateTimeImpl;
034    import railo.runtime.type.scope.ScopeContext;
035    
036    /**
037     * client scope that store it's data in a datasource
038     */
039    public abstract class StorageScopeDatasource extends StorageScopeImpl {
040    
041            private static final long serialVersionUID = 239179599401918216L;
042    
043            public static final Collection.Key EXPIRES = KeyImpl.intern("expires");
044    
045            public static final String PREFIX = "cf";
046            
047            private String datasourceName;
048    
049            private String appName;
050    
051            private String cfid;
052            
053            
054            /**
055             * Constructor of the class
056             * @param pc
057             * @param name
058             * @param sct
059             * @param b 
060             */
061            protected StorageScopeDatasource(PageContext pc,String datasourceName, String strType,int type,Struct sct) { 
062                    super(
063                                    sct,
064                                    type==SCOPE_CLIENT?doNowIfNull(pc,Caster.toDate(sct.get(TIMECREATED,null),false,pc.getTimeZone(),null)):null,
065                                    doNowIfNull(pc,Caster.toDate(sct.get(LASTVISIT,null),false,pc.getTimeZone(),null)),
066                                    -1, 
067                                    type==SCOPE_CLIENT?Caster.toIntValue(sct.get(HITCOUNT,"1"),1):0,
068                                    strType,type);
069    
070                    this.datasourceName=datasourceName; 
071                    appName=pc.getApplicationContext().getName();
072                    cfid=pc.getCFID();
073            }
074    
075            /**
076             * Constructor of the class, clone existing
077             * @param other
078             */
079            protected StorageScopeDatasource(StorageScopeDatasource other,boolean deepCopy) {
080                    super(other,deepCopy);
081                    this.datasourceName=other.datasourceName;
082            }
083            
084            private static DateTime doNowIfNull(PageContext pc,DateTime dt) {
085                    if(dt==null)return new DateTimeImpl(pc.getConfig());
086                    return dt;
087            }
088            
089            
090            
091            
092            protected static Struct _loadData(PageContext pc, String datasourceName,String strType,int type, Log log, boolean mxStyle) throws PageException {
093                    DatasourceConnection dc=null;
094                    Query query=null;
095                // select
096                SQL sqlSelect=mxStyle?
097                                    new SQLImpl("mx"):
098                                    new SQLImpl("select data from "+PREFIX+"_"+strType+"_data where cfid=? and name=? and expires > ?"
099                                                    ,new SQLItem[]{
100                                            new SQLItemImpl(pc.getCFID(),Types.VARCHAR),
101                                            new SQLItemImpl(pc.getApplicationContext().getName(),Types.VARCHAR),
102                                            new SQLItemImpl(now(pc.getConfig()),Types.VARCHAR)
103                                    });
104                    
105                    ConfigImpl config = (ConfigImpl)pc.getConfig();
106                    DatasourceConnectionPool pool = config.getDatasourceConnectionPool();
107                    try {
108                            dc=pool.getDatasourceConnection(pc,config.getDataSource(datasourceName),null,null);
109                            if(!((DataSourceImpl)dc.getDatasource()).isStorage()) 
110                                    throw new ApplicationException("storage usage for this datasource is disabled, you can enable this in the railo administrator.");
111                            query = new QueryImpl(dc,sqlSelect,-1,-1,-1,"query");
112                    }
113                catch (DatabaseException de) {
114                    if(dc==null) throw de;
115                    ScopeContext.info(log,"create table "+PREFIX+"_"+strType+"_data in datasource ["+datasourceName+"]");
116                            try {
117                                    new QueryImpl(dc,createSQL(dc,mxStyle,"text",strType),-1,-1,-1,"query");
118                    }
119                        catch (DatabaseException _de) {
120                            try {
121                                            new QueryImpl(dc,createSQL(dc,mxStyle,"memo",strType),-1,-1,-1,"query");
122                            }
123                                catch (DatabaseException __de) {
124                                    new QueryImpl(dc,createSQL(dc,mxStyle,"clob",strType),-1,-1,-1,"query");
125                                }
126                        }
127                    query = new QueryImpl(dc,sqlSelect,-1,-1,-1,"query");
128                    }
129                finally {
130                    if(dc!=null) pool.releaseDatasourceConnection(dc);
131                }
132                boolean debugUsage=DebuggerImpl.debugQueryUsage(pc,query);
133                ((DebuggerImpl)pc.getDebugger()).addQuery(debugUsage?query:null,datasourceName,"",sqlSelect,query.getRecordcount(),pc.getCurrentPageSource(),query.executionTime());
134                boolean _isNew = query.getRecordcount()==0;
135                
136                if(_isNew) {
137                    ScopeContext.info(log,"create new "+strType+" scope for "+pc.getApplicationContext().getName()+"/"+pc.getCFID()+" in datasource ["+datasourceName+"]");
138                            return null;
139                }
140                String str=Caster.toString(query.get(KeyImpl.DATA));
141                if(mxStyle) return null;
142                Struct s = (Struct)pc.evaluate(str);
143                ScopeContext.info(log,"load existing data from ["+datasourceName+"."+PREFIX+"_"+strType+"_data] to create "+strType+" scope for "+pc.getApplicationContext().getName()+"/"+pc.getCFID());
144                    
145                return s;
146            }
147    
148            /**
149             * @see railo.runtime.type.scope.storage.StorageScopeImpl#touchAfterRequest(railo.runtime.PageContext)
150             */
151            public void touchAfterRequest(PageContext pc) {
152                    setTimeSpan(pc);
153                    super.touchAfterRequest(pc); 
154                    
155                    store(pc.getConfig());
156            }
157            
158            public void store(Config config) {
159                    //if(!super.hasContent()) return;
160                    
161                    DatasourceConnection dc = null;
162                    ConfigImpl ci = (ConfigImpl)config;
163                    DatasourceConnectionPool pool = ci.getDatasourceConnectionPool();
164                    try {
165                            dc=pool.getDatasourceConnection(null,config.getDataSource(datasourceName),null,null);
166                            int recordsAffected = executeUpdate(config,dc.getConnection(),"update "+PREFIX+"_"+getTypeAsString()+"_data set expires=?,data=? where cfid=? and name=?",false);
167                        if(recordsAffected>1) {
168                            executeUpdate(config,dc.getConnection(),"delete from "+PREFIX+"_"+getTypeAsString()+"_data where cfid=? and name=?",true);
169                            recordsAffected=0;
170                        }
171                        if(recordsAffected==0) {
172                            executeUpdate(config,dc.getConnection(),"insert into "+PREFIX+"_"+getTypeAsString()+"_data (expires,data,cfid,name) values(?,?,?,?)",false);
173                        }
174    
175                    } 
176                    catch (Exception e) {}
177                    finally {
178                            if(dc!=null) pool.releaseDatasourceConnection(dc);
179                    }
180            }
181            
182            public void unstore(Config config) {
183                    ConfigImpl ci=(ConfigImpl) config;
184                    DatasourceConnection dc = null;
185                    
186                    
187                    DatasourceConnectionPool pool = ci.getDatasourceConnectionPool();
188                    try {
189                            dc=pool.getDatasourceConnection(null,config.getDataSource(datasourceName),null,null);
190                            executeUpdate(config,dc.getConnection(),"delete from "+PREFIX+"_"+getTypeAsString()+"_data where cfid=? and name=?",true);
191                    } 
192                    catch (Exception e) {}
193                    finally {
194                            if(dc!=null) pool.releaseDatasourceConnection(dc);
195                    }
196            }
197            
198            
199    
200            private int executeUpdate(Config config,Connection conn, String strSQL, boolean ignoreData) throws SQLException, PageException, ConverterException {
201                    //String appName = pc.getApplicationContext().getName();
202                    SQLImpl sql = new SQLImpl(strSQL,new SQLItem[]{
203                                    new SQLItemImpl(createExpires(getTimeSpan(), config),Types.VARCHAR),
204                                    new SQLItemImpl(new ScriptConverter().serializeStruct(sct,ignoreSet),Types.VARCHAR),
205                                    new SQLItemImpl(cfid,Types.VARCHAR),
206                                    new SQLItemImpl(appName,Types.VARCHAR)
207                    });
208                    if(ignoreData)sql = new SQLImpl(strSQL,new SQLItem[]{
209                                    new SQLItemImpl(cfid,Types.VARCHAR),
210                                    new SQLItemImpl(appName,Types.VARCHAR)
211                            });
212                    
213                    return execute(conn, sql);
214            }
215            
216            
217            
218            
219    
220            private static String createExpires(long timespan,Config config) {
221                    return Caster.toString(timespan+new DateTimeImpl(config).getTime());
222            }
223            
224            private static String now(Config config) {
225                    return Caster.toString(new DateTimeImpl(config).getTime());
226            }
227    
228            private static int execute(Connection conn, SQLImpl sql) throws SQLException, PageException {
229                    PreparedStatement preStat = conn.prepareStatement(sql.getSQLString());
230                    int count=0;
231                    try {
232                            SQLItem[] items=sql.getItems();
233                        for(int i=0;i<items.length;i++) {
234                        SQLCaster.setValue(preStat,i+1,items[i]);
235                    }
236                        count= preStat.executeUpdate();
237                    }
238                    finally {
239                        preStat.close();    
240                    }
241                return count;
242            }
243    
244            private static SQL createSQL(DatasourceConnection dc, boolean mxStyle, String textType, String type) {
245                    String clazz = dc.getDatasource().getClazz().getName();
246                    
247                boolean isMSSQL=
248                    clazz.equals("com.microsoft.jdbc.sqlserver.SQLServerDriver") || 
249                    clazz.equals("net.sourceforge.jtds.jdbc.Driver");
250                boolean isHSQLDB=
251                    clazz.equals("org.hsqldb.jdbcDriver");
252                boolean isOracle=
253                    clazz.indexOf("OracleDriver")!=-1;
254                
255                StringBuffer sb=new StringBuffer("CREATE TABLE ");
256                if(mxStyle) {}
257                    else {
258                            if(isMSSQL)sb.append("dbo.");
259                            sb.append(PREFIX+"_"+type+"_data (");
260                            
261                            // expires
262                            sb.append("expires varchar(64) NOT NULL, ");
263                            // cfid
264                            sb.append("cfid varchar(64) NOT NULL, ");
265                            // name
266                            sb.append("name varchar(255) NOT NULL, ");
267                            // data
268                            sb.append("data ");
269                            if(isHSQLDB)sb.append("varchar ");
270                            else if(isOracle)sb.append("CLOB ");
271                            else sb.append(textType+" ");
272                            sb.append(" NOT NULL");
273                    }
274                sb.append(")");
275                    return new SQLImpl(sb.toString());
276            }
277            
278    
279            /**
280             *
281             * @see railo.runtime.type.scope.ClientSupportOld#initialize(railo.runtime.PageContext)
282             */
283            public void touchBeforeRequest(PageContext pc) {
284                    setTimeSpan(pc);
285                    super.touchBeforeRequest(pc);
286            }
287            
288            /**
289             * @see railo.runtime.type.scope.storage.StorageScope#getStorageType()
290             */
291            public String getStorageType() {
292                    return "Datasource";
293            }
294    
295            /**
296             * @return the datasourceName
297             */
298            public String getDatasourceName() {
299                    return datasourceName;
300            }
301    }