2008-01-23 Olivier Dufour <olivier.duff@gmail.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                 private void GetConnectionData (out string providerAssembly,
151                                 out string cncTypeName, out string cncString)
152                 {
153                         providerAssembly = null;
154                         cncTypeName = null;
155                         cncString = null;
156
157                         NameValueCollection config = ConfigurationSettings.AppSettings;
158                         if (config != null) {
159                                 providerAssembly = config ["StateDBProviderAssembly"];
160                                 cncTypeName = config ["StateDBConnectionType"];
161                                 paramPrefix = config ["StateDBParamPrefix"];
162                         }
163
164                         cncString = this.config.SqlConnectionString;
165
166                         if (providerAssembly == null || providerAssembly == String.Empty)
167                                 providerAssembly = "Npgsql.dll";
168
169                         if (cncTypeName == null || cncTypeName == String.Empty)
170                                 cncTypeName = "Npgsql.NpgsqlConnection";
171
172                         if (cncString == null || cncString == String.Empty)
173                                 cncString = "SERVER=127.0.0.1;USER ID=monostate;PASSWORD=monostate;dbname=monostate";
174
175                         if (paramPrefix == null || paramPrefix == String.Empty)
176                                 paramPrefix = defaultParamPrefix;
177                 }
178
179                 IDataReader GetReader (string id)
180                 {
181                         ((IDataParameter)selectCommand.Parameters["SessionID"]).Value = id;
182                         ((IDataParameter)selectCommand.Parameters["Expires"]).Value = DateTime.Now;
183                         ((IDataParameter)selectCommand.Parameters["AppPath"]).Value = this.AppPath;
184                         return selectCommand.ExecuteReader ();
185                 }
186
187                 IDataReader GetReaderWithRetry (string id)
188                 {
189                         try {
190                                 return GetReader (id);
191                         } catch {
192                         }
193
194                         try {
195                                 DisposeConnection();
196                         } catch {
197                         }
198
199                         InitializeConnection();
200                         return GetReader (id);
201                 }
202
203                 private HttpSessionState SelectSession (string id, bool read_only)
204                 {
205                         HttpSessionState session = null;
206                         using (IDataReader reader = GetReaderWithRetry (id)) {
207                                 if (!reader.Read ())
208                                         return null;
209
210                                 SessionDictionary dict; 
211                                 HttpStaticObjectsCollection sobjs;
212                                 int timeout;
213                                 
214                                 dict = SessionDictionary.FromByteArray (ReadBytes (reader, reader.FieldCount-1));
215                                 sobjs = HttpStaticObjectsCollection.FromByteArray (ReadBytes (reader, reader.FieldCount-2));
216                                 // try to support as many DBs/int types as possible
217                                 timeout = Convert.ToInt32 (reader.GetValue (reader.FieldCount-3));
218                                 
219                                 session = new HttpSessionState (id, dict, sobjs, timeout, false, config.CookieLess,
220                                                 SessionStateMode.SQLServer, read_only);
221                                 return session;
222                         }
223                 }
224
225                 void InsertSession (HttpSessionState session, int timeout)
226                 {
227                         ((IDataParameter)insertCommand.Parameters["SessionID"]).Value = session.SessionID;
228                         ((IDataParameter)insertCommand.Parameters["AppPath"]).Value = this.AppPath;
229                         ((IDataParameter)insertCommand.Parameters["Created"]).Value = DateTime.Now;
230                         ((IDataParameter)insertCommand.Parameters["Expires"]).Value = DateTime.Now.AddMinutes (timeout);
231                         ((IDataParameter)insertCommand.Parameters["Timeout"]).Value = timeout;
232                         ((IDataParameter)insertCommand.Parameters["StaticObjectsData"]).Value = session.StaticObjects.ToByteArray ();
233                         ((IDataParameter)insertCommand.Parameters["SessionData"]).Value = session.SessionDictionary.ToByteArray ();
234                         insertCommand.ExecuteNonQuery ();
235                 }
236
237                 void InsertSessionWithRetry (HttpSessionState session, int timeout)
238                 {
239                         try {
240                                 InsertSession (session, timeout);
241                                 return;
242                         } catch {
243                         }
244
245                         try {
246                                 DisposeConnection ();
247                         } catch {
248                         }
249
250                         InitializeConnection ();
251                         InsertSession (session, timeout);
252                 }
253
254                 void UpdateSession (string id, int timeout, SessionDictionary dict)
255                 {
256                         ((IDataParameter)updateCommand.Parameters["SessionID"]).Value = id;
257                         ((IDataParameter)updateCommand.Parameters["Expires"]).Value = DateTime.Now.AddMinutes (timeout);
258                         ((IDataParameter)updateCommand.Parameters["Timeout"]).Value = timeout;
259                         ((IDataParameter)updateCommand.Parameters["SessionData"]).Value = dict.ToByteArray ();
260                         
261                         updateCommand.ExecuteNonQuery ();
262                 }
263
264                 void UpdateSessionWithRetry (string id, int timeout, SessionDictionary dict)
265                 {
266                         try {
267                                 UpdateSession (id, timeout, dict);
268                                 return;
269                         } catch {
270                         }
271
272                         try {
273                                 DisposeConnection ();
274                         } catch {
275                         }
276
277                         InitializeConnection ();
278                         UpdateSession (id, timeout, dict);
279                 }
280
281                 void DeleteSession (string id)
282                 {
283                         
284                         ((IDataParameter)deleteCommand.Parameters["SessionID"]).Value = id;
285                         deleteCommand.ExecuteNonQuery ();
286                 }
287
288                 void DeleteSessionWithRetry (string id)
289                 {
290                         try {
291                                 DeleteSession (id);
292                                 return;
293                         } catch {
294                         }
295
296                         try {
297                                 DisposeConnection ();
298                         } catch {
299                         }
300
301                         InitializeConnection ();
302                         DeleteSession (id);
303                 }
304
305                 void InitializeConnection()
306                 {
307                         cnc.Open ();
308                         selectCommand = cnc.CreateCommand ();
309                         selectCommand.CommandText = selectCommandText;
310                         selectCommand.Parameters.Add (CreateParam (selectCommand, DbType.String, "SessionID", String.Empty));
311                         selectCommand.Parameters.Add (CreateParam (selectCommand, DbType.DateTime, "Expires", DateTime.MinValue ));
312                         selectCommand.Parameters.Add (CreateParam (selectCommand, DbType.String, "AppPath", String.Empty));
313                         selectCommand.Prepare ();
314                         
315                         insertCommand = cnc.CreateCommand ();
316                         insertCommand.CommandText = insertCommandText;
317                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.String, "SessionID", String.Empty));
318                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.String, "AppPath", String.Empty));
319                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.DateTime, "Created", DateTime.MinValue));
320                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.DateTime, "Expires", DateTime.MinValue));
321                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.Int32, "Timeout", 0));
322                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.Binary, "StaticObjectsData",new byte[0] ));
323                         insertCommand.Parameters.Add (CreateParam (insertCommand, DbType.Binary, "SessionData",
324                                                    new byte[0]));
325                         insertCommand.Prepare();
326                         
327                         updateCommand = cnc.CreateCommand ();
328                         updateCommand.CommandText = updateCommandText;
329                         updateCommand.Parameters.Add (CreateParam (updateCommand, DbType.String, "SessionID", String.Empty));
330                         updateCommand.Parameters.Add (CreateParam (updateCommand, DbType.DateTime, "Expires", DateTime.MinValue));
331                         updateCommand.Parameters.Add (CreateParam (updateCommand, DbType.Int32, "Timeout", 0));
332                         updateCommand.Parameters.Add (CreateParam (updateCommand, DbType.Binary, "SessionData",
333                                                                 new byte[0]));
334                         updateCommand.Prepare();
335                         
336                         deleteCommand = cnc.CreateCommand ();
337                         deleteCommand.CommandText = deleteCommandText;
338                         deleteCommand.Parameters.Add (CreateParam (deleteCommand, DbType.String, "SessionID", String.Empty));
339                         deleteCommand.Prepare();
340                 }
341                 
342                 void DisposeConnection()
343                 {
344                         selectCommand.Dispose();
345                         insertCommand.Dispose();
346                         updateCommand.Dispose();
347                         deleteCommand.Dispose();
348                         cnc.Close();
349                 }
350                 
351                 private IDataParameter CreateParam (IDbCommand command, DbType type,
352                                 string name, object value)
353                 {
354                         IDataParameter result = command.CreateParameter ();
355                         result.DbType = type;
356                         result.ParameterName = paramPrefix + name;
357                         result.Value = value;
358                         return result;
359                 }
360
361                 private byte [] ReadBytes (IDataReader reader, int index)
362                 {
363                         int len = (int) reader.GetBytes (index, 0, null, 0, 0);
364                         byte [] data = new byte [len];
365                         reader.GetBytes (index, 0, data, 0, len);
366                         return data;
367                 }
368         }
369 }
370 #endif