2008-09-26 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.SessionState / SessionSQLServerHandler.cs
1 //
2 // System.Web.SessionState.SessionSQLServerHandler
3 //
4 // Author(s):
5 //  Jackson Harper (jackson@ximian.com)
6 //
7 // (C) 2003 Novell, Inc. (http://www.novell.com), All rights reserved
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 #if !NET_2_0
31 using System;
32 using System.IO;
33 using System.Data;
34 using System.Reflection;
35 using System.Configuration;
36 using System.Collections.Specialized;
37 using System.Web.Configuration;
38
39 namespace System.Web.SessionState {
40
41         internal class SessionSQLServerHandler : ISessionHandler
42         {
43                 private static Type cncType = null;
44                 private IDbConnection cnc = null;
45                 private SessionConfig config;
46                 private string AppPath = String.Empty;
47                 
48                 const string defaultParamPrefix = ":";
49                 string paramPrefix;
50                 string selectCommandText = "SELECT timeout,staticobjectsdata,sessiondata FROM ASPStateTempSessions WHERE SessionID = :SessionID AND Expires > :Expires AND AppPath = :AppPath";
51                 IDbCommand selectCommand = null;
52                 string insertCommandText = "INSERT INTO ASPStateTempSessions (SessionId, AppPath, Created, expires, timeout, StaticObjectsData, SessionData)  VALUES (:SessionID, :AppPath, :Created, :Expires, :Timeout, :StaticObjectsData, :SessionData)";
53                 IDbCommand insertCommand = null;
54                 string updateCommandText = "UPDATE ASPStateTempSessions SET expires = :Expires, timeout = :Timeout, SessionData = :SessionData WHERE SessionId = :SessionID";
55                 IDbCommand updateCommand = null;
56                 string deleteCommandText = "DELETE FROM ASPStateTempSessions WHERE SessionId = :SessionID";
57                 IDbCommand deleteCommand = null;
58
59                 public void Dispose ()
60                 {
61                         if (cnc != null) {
62                                 cnc.Close ();
63                                 cnc = null;
64                         }
65                 }
66
67                 public void Init (SessionStateModule module, HttpApplication context,
68                                   SessionConfig config)
69                 {
70                         string connectionTypeName;
71                         string providerAssemblyName;
72                         string cncString;
73
74                         this.config = config;
75                         this.AppPath = context.Request.ApplicationPath;
76                         
77                         GetConnectionData (out providerAssemblyName, out connectionTypeName, out cncString);
78                         if (cncType == null) {
79                                 Assembly dbAssembly = Assembly.Load (providerAssemblyName);
80                                 cncType = dbAssembly.GetType (connectionTypeName, true);
81                                 if (!typeof (IDbConnection).IsAssignableFrom (cncType))
82                                         throw new ApplicationException ("The type '" + cncType +
83                                                         "' does not implement IDB Connection.\n" +
84                                                         "Check 'DbConnectionType' in server.exe.config.");
85                         }
86
87                         cnc = (IDbConnection) Activator.CreateInstance (cncType);
88                         cnc.ConnectionString = cncString;
89                         try {
90                                 InitializeConnection ();
91                         } catch (Exception exc) {
92                                 cnc = null;
93                                 throw exc;
94                         }
95
96                         if (paramPrefix != defaultParamPrefix) {
97                                 ReplaceParamPrefix (ref selectCommandText);
98                                 ReplaceParamPrefix (ref insertCommandText);
99                                 ReplaceParamPrefix (ref updateCommandText);
100                                 ReplaceParamPrefix (ref deleteCommandText);
101                         }
102                 }
103
104                 void ReplaceParamPrefix(ref string command)
105                 {
106                         command = command.Replace (defaultParamPrefix, paramPrefix);
107                 }
108
109                 public void UpdateHandler (HttpContext context, SessionStateModule module)
110                 {
111                         HttpSessionState session = context.Session;
112                         if (session == null || session.IsReadOnly)
113                                 return;
114
115                         string id = session.SessionID;
116                         if (!session._abandoned) {
117                                 SessionDictionary dict = session.SessionDictionary;
118                                 UpdateSessionWithRetry (id, session.Timeout, dict);
119                         } else {
120                                 DeleteSessionWithRetry (id);
121                         }
122                 }
123
124                 public HttpSessionState UpdateContext (HttpContext context, SessionStateModule module,
125                                                         bool required, bool read_only, ref bool isNew)
126                 {
127                         if (!required)
128                                 return null;
129
130                         HttpSessionState session = null;
131                         string id = SessionId.Lookup (context.Request, config.CookieLess);
132
133                         if (id != null) {
134                                 session = SelectSession (id, read_only);
135                                 if (session != null)
136                                         return session;
137                         }
138
139                         id = SessionId.Create ();
140                         session = new HttpSessionState (id, new SessionDictionary (),
141                                         HttpApplicationFactory.ApplicationState.SessionObjects,
142                                         config.Timeout,
143                                         true, config.CookieLess, SessionStateMode.SQLServer, read_only);
144
145                         InsertSessionWithRetry (session, config.Timeout);
146                         isNew = true;
147                         return session;
148                 }
149
150                 public void Touch (string sessionId, int timeout)
151                 {
152                         HttpContext ctx = HttpContext.Current;
153                         if (ctx == null)
154                                 return;
155                         
156                         HttpSessionState session = ctx.Session;
157                         if (session == null)
158                                 return;
159                         
160                         UpdateSession (sessionId, timeout, session.SessionDictionary);
161                 }
162                 
163                 private void GetConnectionData (out string providerAssembly,
164                                 out string cncTypeName, out string cncString)
165                 {
166                         providerAssembly = null;
167                         cncTypeName = null;
168                         cncString = null;
169
170                         NameValueCollection config = ConfigurationSettings.AppSettings;
171                         if (config != null) {
172                                 providerAssembly = config ["StateDBProviderAssembly"];
173                                 cncTypeName = config ["StateDBConnectionType"];
174                                 paramPrefix = config ["StateDBParamPrefix"];
175                         }
176
177                         cncString = this.config.SqlConnectionString;
178
179                         if (providerAssembly == null || providerAssembly == String.Empty)
180                                 providerAssembly = "Npgsql.dll";
181
182                         if (cncTypeName == null || cncTypeName == String.Empty)
183                                 cncTypeName = "Npgsql.NpgsqlConnection";
184
185                         if (cncString == null || cncString == String.Empty)
186                                 cncString = "SERVER=127.0.0.1;USER ID=monostate;PASSWORD=monostate;dbname=monostate";
187
188                         if (paramPrefix == null || paramPrefix == String.Empty)
189                                 paramPrefix = defaultParamPrefix;
190                 }
191
192                 IDataReader GetReader (string id)
193                 {
194                         ((IDataParameter)selectCommand.Parameters["SessionID"]).Value = id;
195                         ((IDataParameter)selectCommand.Parameters["Expires"]).Value = DateTime.Now;
196                         ((IDataParameter)selectCommand.Parameters["AppPath"]).Value = this.AppPath;
197                         return selectCommand.ExecuteReader ();
198                 }
199
200                 IDataReader GetReaderWithRetry (string id)
201                 {
202                         try {
203                                 return GetReader (id);
204                         } catch {
205                         }
206
207                         try {
208                                 DisposeConnection();
209                         } catch {
210                         }
211
212                         InitializeConnection();
213                         return GetReader (id);
214                 }
215
216                 private HttpSessionState SelectSession (string id, bool read_only)
217                 {
218                         HttpSessionState session = null;
219                         using (IDataReader reader = GetReaderWithRetry (id)) {
220                                 if (!reader.Read ())
221                                         return null;
222
223                                 SessionDictionary dict; 
224                                 HttpStaticObjectsCollection sobjs;
225                                 int timeout;
226                                 
227                                 dict = SessionDictionary.FromByteArray (ReadBytes (reader, reader.FieldCount-1));
228                                 sobjs = HttpStaticObjectsCollection.FromByteArray (ReadBytes (reader, reader.FieldCount-2));
229                                 // try to support as many DBs/int types as possible
230                                 timeout = Convert.ToInt32 (reader.GetValue (reader.FieldCount-3));
231                                 
232                                 session = new HttpSessionState (id, dict, sobjs, timeout, false, config.CookieLess,
233                                                 SessionStateMode.SQLServer, read_only);
234                                 return session;
235                         }
236                 }
237
238                 void InsertSession (HttpSessionState session, int timeout)
239                 {
240                         ((IDataParameter)insertCommand.Parameters["SessionID"]).Value = session.SessionID;
241                         ((IDataParameter)insertCommand.Parameters["AppPath"]).Value = this.AppPath;
242                         ((IDataParameter)insertCommand.Parameters["Created"]).Value = DateTime.Now;
243                         ((IDataParameter)insertCommand.Parameters["Expires"]).Value = DateTime.Now.AddMinutes (timeout);
244                         ((IDataParameter)insertCommand.Parameters["Timeout"]).Value = timeout;
245                         ((IDataParameter)insertCommand.Parameters["StaticObjectsData"]).Value = session.StaticObjects.ToByteArray ();
246                         ((IDataParameter)insertCommand.Parameters["SessionData"]).Value = session.SessionDictionary.ToByteArray ();
247                         insertCommand.ExecuteNonQuery ();
248                 }
249
250                 void InsertSessionWithRetry (HttpSessionState session, int timeout)
251                 {
252                         try {
253                                 InsertSession (session, timeout);
254                                 return;
255                         } catch {
256                         }
257
258                         try {
259                                 DisposeConnection ();
260                         } catch {
261                         }
262
263                         InitializeConnection ();
264                         InsertSession (session, timeout);
265                 }
266
267                 void UpdateSession (string id, int timeout, SessionDictionary dict)
268                 {
269                         ((IDataParameter)updateCommand.Parameters["SessionID"]).Value = id;
270                         ((IDataParameter)updateCommand.Parameters["Expires"]).Value = DateTime.Now.AddMinutes (timeout);
271                         ((IDataParameter)updateCommand.Parameters["Timeout"]).Value = timeout;
272                         ((IDataParameter)updateCommand.Parameters["SessionData"]).Value = dict.ToByteArray ();
273                         
274                         updateCommand.ExecuteNonQuery ();
275                 }
276
277                 void UpdateSessionWithRetry (string id, int timeout, SessionDictionary dict)
278                 {
279                         try {
280                                 UpdateSession (id, timeout, dict);
281                                 return;
282                         } catch {
283                         }
284
285                         try {
286                                 DisposeConnection ();
287                         } catch {
288                         }
289
290                         InitializeConnection ();
291                         UpdateSession (id, timeout, dict);
292                 }
293
294                 void DeleteSession (string id)
295                 {
296                         
297                         ((IDataParameter)deleteCommand.Parameters["SessionID"]).Value = id;
298                         deleteCommand.ExecuteNonQuery ();
299                 }
300
301                 void DeleteSessionWithRetry (string id)
302                 {
303                         try {
304                                 DeleteSession (id);
305                                 return;
306                         } catch {
307                         }
308
309                         try {
310                                 DisposeConnection ();
311                         } catch {
312                         }
313
314                         InitializeConnection ();
315                         DeleteSession (id);
316                 }
317
318                 void InitializeConnection()
319                 {
320                         cnc.Open ();
321                         selectCommand = cnc.CreateCommand ();
322                         selectCommand.CommandText = selectCommandText;
323                         selectCommand.Parameters.Add (CreateParam (selectCommand, DbType.String, "SessionID", String.Empty));
324                         selectCommand.Parameters.Add (CreateParam (selectCommand, DbType.DateTime, "Expires", DateTime.MinValue ));
325                         selectCommand.Parameters.Add (CreateParam (selectCommand, DbType.String, "AppPath", String.Empty));
326                         selectCommand.Prepare ();
327                         
328                         insertCommand = cnc.CreateCommand ();
329                         insertCommand.CommandText = insertCommandText;
330                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.String, "SessionID", String.Empty));
331                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.String, "AppPath", String.Empty));
332                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.DateTime, "Created", DateTime.MinValue));
333                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.DateTime, "Expires", DateTime.MinValue));
334                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.Int32, "Timeout", 0));
335                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.Binary, "StaticObjectsData",new byte[0] ));
336                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.Binary, "SessionData",
337                                                    new byte[0]));
338                         insertCommand.Prepare();
339                         
340                         updateCommand = cnc.CreateCommand ();
341                         updateCommand.CommandText = updateCommandText;
342                         updateCommand.Parameters.Add (CreateParam (updateCommand, DbType.String, "SessionID", String.Empty));
343                         updateCommand.Parameters.Add (CreateParam (updateCommand, DbType.DateTime, "Expires", DateTime.MinValue));
344                         updateCommand.Parameters.Add (CreateParam (updateCommand, DbType.Int32, "Timeout", 0));
345                         updateCommand.Parameters.Add (CreateParam (updateCommand, DbType.Binary, "SessionData",
346                                                                 new byte[0]));
347                         updateCommand.Prepare();
348                         
349                         deleteCommand = cnc.CreateCommand ();
350                         deleteCommand.CommandText = deleteCommandText;
351                         deleteCommand.Parameters.Add (CreateParam (deleteCommand, DbType.String, "SessionID", String.Empty));
352                         deleteCommand.Prepare();
353                 }
354                 
355                 void DisposeConnection()
356                 {
357                         selectCommand.Dispose();
358                         insertCommand.Dispose();
359                         updateCommand.Dispose();
360                         deleteCommand.Dispose();
361                         cnc.Close();
362                 }
363                 
364                 private IDataParameter CreateParam (IDbCommand command, DbType type,
365                                 string name, object value)
366                 {
367                         IDataParameter result = command.CreateParameter ();
368                         result.DbType = type;
369                         result.ParameterName = paramPrefix + name;
370                         result.Value = value;
371                         return result;
372                 }
373
374                 private byte [] ReadBytes (IDataReader reader, int index)
375                 {
376                         int len = (int) reader.GetBytes (index, 0, null, 0, 0);
377                         byte [] data = new byte [len];
378                         reader.GetBytes (index, 0, data, 0, len);
379                         return data;
380                 }
381         }
382 }
383 #endif