2008-11-18 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Profile / SqliteProfileProvider.cs
1 //
2 // $Id: PgProfileProvider.cs 36 2007-11-24 09:44:42Z dna $
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
11 // 
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 // 
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 //
23 // Copyright © 2006, 2007 Nauck IT KG           http://www.nauck-it.de
24 //
25 // Author:
26 //      Daniel Nauck            <d.nauck(at)nauck-it.de>
27 //
28 // Adapted to Sqlite by Marek Habersack <mhabersack@novell.com>
29 //
30
31 #if NET_2_0
32 using System;
33 using System.Data;
34 using System.Data.Common;
35 using System.Configuration;
36 using System.Configuration.Provider;
37 using System.Collections.Generic;
38 using System.Collections.Specialized;
39 using System.Diagnostics;
40 using System.Text;
41 using System.Web.Hosting;
42 using System.Web.Util;
43
44 using Mono.Data.Sqlite;
45
46 namespace System.Web.Profile
47 {
48         internal class SqliteProfileProvider : ProfileProvider
49         {
50                 const string m_ProfilesTableName = "Profiles";
51                 const string m_ProfileDataTableName = "ProfileData";
52                 string m_ConnectionString = string.Empty;
53
54                 SerializationHelper m_serializationHelper = new SerializationHelper();
55
56                 DbParameter AddParameter (DbCommand command, string parameterName)
57                 {
58                         return AddParameter (command, parameterName, null);
59                 }
60                 
61                 DbParameter AddParameter (DbCommand command, string parameterName, object parameterValue)
62                 {
63                         return AddParameter (command, parameterName, ParameterDirection.Input, parameterValue);
64                 }
65
66                 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, object parameterValue)
67                 {
68                         DbParameter dbp = command.CreateParameter ();
69                         dbp.ParameterName = parameterName;
70                         dbp.Value = parameterValue;
71                         dbp.Direction = direction;
72                         command.Parameters.Add (dbp);
73                         return dbp;
74                 }
75                 
76                 /// <summary>
77                 /// System.Configuration.Provider.ProviderBase.Initialize Method
78                 /// </summary>
79                 public override void Initialize(string name, NameValueCollection config)
80                 {
81                         // Initialize values from web.config.
82                         if (config == null)
83                                 throw new ArgumentNullException("Config", Properties.Resources.ErrArgumentNull);
84
85                         if (string.IsNullOrEmpty(name))
86                                 name = Properties.Resources.ProfileProviderDefaultName;
87
88                         if (string.IsNullOrEmpty(config["description"]))
89                         {
90                                 config.Remove("description");
91                                 config.Add("description", Properties.Resources.ProfileProviderDefaultDescription);
92                         }
93
94                         // Initialize the abstract base class.
95                         base.Initialize(name, config);
96
97                         m_ApplicationName = GetConfigValue(config["applicationName"], HostingEnvironment.ApplicationVirtualPath);
98
99                         // Get connection string.
100                         string connStrName = config["connectionStringName"];
101
102                         if (string.IsNullOrEmpty(connStrName))
103                         {
104                                 throw new ArgumentOutOfRangeException("ConnectionStringName", Properties.Resources.ErrArgumentNullOrEmpty);
105                         }
106                         else
107                         {
108                                 ConnectionStringSettings ConnectionStringSettings = ConfigurationManager.ConnectionStrings[connStrName];
109
110                                 if (ConnectionStringSettings == null || string.IsNullOrEmpty(ConnectionStringSettings.ConnectionString.Trim()))
111                                 {
112                                         throw new ProviderException(Properties.Resources.ErrConnectionStringNullOrEmpty);
113                                 }
114
115                                 m_ConnectionString = ConnectionStringSettings.ConnectionString;
116                         }
117                 }
118
119                 /// <summary>
120                 /// System.Web.Profile.ProfileProvider properties.
121                 /// </summary>
122                 #region System.Web.Security.ProfileProvider properties
123                 string m_ApplicationName = string.Empty;
124
125                 public override string ApplicationName
126                 {
127                         get { return m_ApplicationName; }
128                         set { m_ApplicationName = value; }
129                 }
130                 #endregion
131
132                 /// <summary>
133                 /// System.Web.Profile.ProfileProvider methods.
134                 /// </summary>
135                 #region System.Web.Security.ProfileProvider methods
136
137                 /// <summary>
138                 /// ProfileProvider.DeleteInactiveProfiles
139                 /// </summary>
140                 public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
141                 {
142                         throw new Exception("DeleteInactiveProfiles: The method or operation is not implemented.");
143                 }
144
145                 public override int DeleteProfiles(string[] usernames)
146                 {
147                         throw new Exception("DeleteProfiles1: The method or operation is not implemented.");
148                 }
149
150                 public override int DeleteProfiles(ProfileInfoCollection profiles)
151                 {
152                         throw new Exception("DeleteProfiles2: The method or operation is not implemented.");
153                 }
154
155                 public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
156                 {
157                         throw new Exception("FindInactiveProfilesByUserName: The method or operation is not implemented.");
158                 }
159
160                 public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
161                 {
162                         throw new Exception("FindProfilesByUserName: The method or operation is not implemented.");
163                 }
164
165                 public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
166                 {
167                         throw new Exception("GetAllInactiveProfiles: The method or operation is not implemented.");
168                 }
169
170                 public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
171                 {
172                         throw new Exception("GetAllProfiles: The method or operation is not implemented.");
173                 }
174
175                 public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
176                 {
177                         throw new Exception("GetNumberOfInactiveProfiles: The method or operation is not implemented.");
178                 }
179                 #endregion
180
181                 /// <summary>
182                 /// System.Configuration.SettingsProvider methods.
183                 /// </summary>
184                 #region System.Web.Security.SettingsProvider methods
185
186                 /// <summary>
187                 /// 
188                 /// </summary>
189                 public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
190                 {
191                         SettingsPropertyValueCollection result = new SettingsPropertyValueCollection();
192                         string username = (string)context["UserName"];
193                         bool isAuthenticated = (bool)context["IsAuthenticated"];
194                         Dictionary<string, object> databaseResult = new Dictionary<string, object>();
195
196                         using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
197                         {
198                                 using (SqliteCommand dbCommand = dbConn.CreateCommand())
199                                 {
200                                         dbCommand.CommandText = string.Format("SELECT \"Name\", \"ValueString\", \"ValueBinary\" FROM \"{0}\" WHERE \"Profile\" = (SELECT \"pId\" FROM \"{1}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName AND \"IsAnonymous\" = @IsAuthenticated)", m_ProfileDataTableName, m_ProfilesTableName);
201
202                                         AddParameter (dbCommand, "@Username", username);
203                                         AddParameter (dbCommand, "@ApplicationName", m_ApplicationName);
204                                         AddParameter (dbCommand, "@IsAuthenticated", !isAuthenticated);
205
206                                         try
207                                         {
208                                                 dbConn.Open();
209                                                 dbCommand.Prepare();
210
211                                                 using (SqliteDataReader reader = dbCommand.ExecuteReader())
212                                                 {
213                                                         while (reader.Read())
214                                                         {
215                                                                 object resultData = null;
216                                                                 if(!reader.IsDBNull(1))
217                                                                         resultData = reader.GetValue(1);
218                                                                 else if(!reader.IsDBNull(2))
219                                                                         resultData = reader.GetValue(2);
220
221                                                                 databaseResult.Add(reader.GetString(0), resultData);
222                                                         }
223                                                 }
224                                         }
225                                         catch (SqliteException e)
226                                         {
227                                                 Trace.WriteLine(e.ToString());
228                                                 throw new ProviderException(Properties.Resources.ErrOperationAborted);
229                                         }
230                                         finally
231                                         {
232                                                 if (dbConn != null)
233                                                         dbConn.Close();
234                                         }
235                                 }
236                         }
237
238                         foreach (SettingsProperty item in collection)
239                         {
240                                 if (item.SerializeAs == SettingsSerializeAs.ProviderSpecific)
241                                 {
242                                         if (item.PropertyType.IsPrimitive || item.PropertyType.Equals(typeof(string)))
243                                                 item.SerializeAs = SettingsSerializeAs.String;
244                                         else
245                                                 item.SerializeAs = SettingsSerializeAs.Xml;
246                                 }
247
248                                 SettingsPropertyValue itemValue = new SettingsPropertyValue(item);
249
250                                 if ((databaseResult.ContainsKey(item.Name)) && (databaseResult[item.Name] != null))
251                                 {
252                                         if(item.SerializeAs == SettingsSerializeAs.String)
253                                                 itemValue.PropertyValue = m_serializationHelper.DeserializeFromBase64((string)databaseResult[item.Name]);
254                                         
255                                         else if (item.SerializeAs == SettingsSerializeAs.Xml)
256                                                 itemValue.PropertyValue = m_serializationHelper.DeserializeFromXml((string)databaseResult[item.Name]);
257
258                                         else if (item.SerializeAs == SettingsSerializeAs.Binary)
259                                                 itemValue.PropertyValue = m_serializationHelper.DeserializeFromBinary((byte[])databaseResult[item.Name]);
260                                 }
261                                 itemValue.IsDirty = false;                              
262                                 result.Add(itemValue);
263                         }
264
265                         UpdateActivityDates(username, isAuthenticated, true);
266
267                         return result;
268                 }
269
270                 public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
271                 {
272                         string username = (string)context["UserName"];
273                         bool isAuthenticated = (bool)context["IsAuthenticated"];
274
275                         if (collection.Count < 1)
276                                 return;
277
278                         if (!ProfileExists(username))
279                                 CreateProfileForUser(username, isAuthenticated);
280
281                         using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
282                         {
283                                 using (SqliteCommand deleteCommand = dbConn.CreateCommand(),
284                                         insertCommand = dbConn.CreateCommand())
285                                 {
286                                         deleteCommand.CommandText = string.Format("DELETE FROM \"{0}\" WHERE \"Name\" = @Name AND \"Profile\" = (SELECT \"pId\" FROM \"{1}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName AND \"IsAnonymous\" = @IsAuthenticated)", m_ProfileDataTableName, m_ProfilesTableName);
287
288                                         AddParameter (deleteCommand, "@Name");
289                                         AddParameter (deleteCommand, "@Username", username);
290                                         AddParameter (deleteCommand, "@ApplicationName", m_ApplicationName);
291                                         AddParameter (deleteCommand, "@IsAuthenticated", !isAuthenticated);
292
293
294                                         insertCommand.CommandText = string.Format("INSERT INTO \"{0}\" (\"pId\", \"Profile\", \"Name\", \"ValueString\", \"ValueBinary\") VALUES (@pId, (SELECT \"pId\" FROM \"{1}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName AND \"IsAnonymous\" = @IsAuthenticated), @Name, @ValueString, @ValueBinary)", m_ProfileDataTableName, m_ProfilesTableName);
295
296                                         AddParameter (insertCommand, "@pId");
297                                         AddParameter (insertCommand, "@Name");
298                                         AddParameter (insertCommand, "@ValueString");
299                                         insertCommand.Parameters["@ValueString"].IsNullable = true;
300                                         AddParameter (insertCommand, "@ValueBinary");
301                                         insertCommand.Parameters["@ValueBinary"].IsNullable = true;
302                                         AddParameter (insertCommand, "@Username", username);
303                                         AddParameter (insertCommand, "@ApplicationName", m_ApplicationName);
304                                         AddParameter (insertCommand, "@IsAuthenticated", !isAuthenticated);
305
306                                         SqliteTransaction dbTrans = null;
307
308                                         try
309                                         {
310                                                 dbConn.Open();
311                                                 deleteCommand.Prepare();
312                                                 insertCommand.Prepare();
313
314                                                 using (dbTrans = dbConn.BeginTransaction())
315                                                 {
316
317                                                         foreach (SettingsPropertyValue item in collection)
318                                                         {
319                                                                 if (!item.IsDirty)
320                                                                         continue;
321
322                                                                 deleteCommand.Parameters["@Name"].Value = item.Name;
323
324                                                                 insertCommand.Parameters["@pId"].Value = Guid.NewGuid().ToString();
325                                                                 insertCommand.Parameters["@Name"].Value = item.Name;
326
327                                                                 if (item.Property.SerializeAs == SettingsSerializeAs.String)
328                                                                 {
329                                                                         insertCommand.Parameters["@ValueString"].Value = m_serializationHelper.SerializeToBase64(item.PropertyValue);
330                                                                         insertCommand.Parameters["@ValueBinary"].Value = DBNull.Value; //new byte[0];//DBNull.Value;
331                                                                 }
332                                                                 else if (item.Property.SerializeAs == SettingsSerializeAs.Xml)
333                                                                 {
334                                                                         item.SerializedValue = m_serializationHelper.SerializeToXml(item.PropertyValue);
335                                                                         insertCommand.Parameters["@ValueString"].Value = item.SerializedValue;
336                                                                         insertCommand.Parameters["@ValueBinary"].Value = DBNull.Value; //new byte[0];//DBNull.Value;
337                                                                 }
338                                                                 else if (item.Property.SerializeAs == SettingsSerializeAs.Binary)
339                                                                 {
340                                                                         item.SerializedValue = m_serializationHelper.SerializeToBinary(item.PropertyValue);
341                                                                         insertCommand.Parameters["@ValueString"].Value = DBNull.Value; //string.Empty;//DBNull.Value;
342                                                                         insertCommand.Parameters["@ValueBinary"].Value = item.SerializedValue;
343                                                                 }
344
345                                                                 deleteCommand.ExecuteNonQuery();
346                                                                 insertCommand.ExecuteNonQuery();
347                                                         }
348
349                                                         UpdateActivityDates(username, isAuthenticated, false);
350
351                                                         // Attempt to commit the transaction
352                                                         dbTrans.Commit();
353                                                 }
354                                         }
355                                         catch (SqliteException e)
356                                         {
357                                                 Trace.WriteLine(e.ToString());
358
359                                                 try
360                                                 {
361                                                         // Attempt to roll back the transaction
362                                                         Trace.WriteLine(Properties.Resources.LogRollbackAttempt);
363                                                         dbTrans.Rollback();
364                                                 }
365                                                 catch (SqliteException re)
366                                                 {
367                                                         // Rollback failed
368                                                         Trace.WriteLine(Properties.Resources.ErrRollbackFailed);
369                                                         Trace.WriteLine(re.ToString());
370                                                 }
371
372                                                 throw new ProviderException(Properties.Resources.ErrOperationAborted);
373                                         }
374                                         finally
375                                         {
376                                                 if (dbConn != null)
377                                                         dbConn.Close();
378                                         }
379                                 }
380                         }
381                 }
382                 #endregion
383
384                 #region private methods
385                 /// <summary>
386                 /// Create a empty user profile
387                 /// </summary>
388                 /// <param name="username"></param>
389                 /// <param name="isAuthenticated"></param>
390                 void CreateProfileForUser(string username, bool isAuthenticated)
391                 {
392                         if (ProfileExists(username))
393                         {
394                                 throw new ProviderException(string.Format(Properties.Resources.ErrProfileAlreadyExist, username));
395                         }
396
397                         using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
398                         {
399                                 using (SqliteCommand dbCommand = dbConn.CreateCommand())
400                                 {
401                                         dbCommand.CommandText = string.Format("INSERT INTO \"{0}\" (\"pId\", \"Username\", \"ApplicationName\", \"IsAnonymous\", \"LastActivityDate\", \"LastUpdatedDate\") Values (@pId, @Username, @ApplicationName, @IsAuthenticated, @LastActivityDate, @LastUpdatedDate)", m_ProfilesTableName);
402
403                                         AddParameter (dbCommand, "@pId", Guid.NewGuid().ToString());
404                                         AddParameter (dbCommand, "@Username", username);
405                                         AddParameter (dbCommand, "@ApplicationName", m_ApplicationName);
406                                         AddParameter (dbCommand, "@IsAuthenticated", !isAuthenticated);
407                                         AddParameter (dbCommand, "@LastActivityDate", DateTime.Now);
408                                         AddParameter (dbCommand, "@LastUpdatedDate", DateTime.Now);
409
410                                         try
411                                         {
412                                                 dbConn.Open();
413                                                 dbCommand.Prepare();
414
415                                                 dbCommand.ExecuteNonQuery();
416                                         }
417                                         catch (SqliteException e)
418                                         {
419                                                 Trace.WriteLine(e.ToString());
420                                                 throw new ProviderException(Properties.Resources.ErrOperationAborted);
421                                         }
422                                         finally
423                                         {
424                                                 if (dbConn != null)
425                                                         dbConn.Close();
426                                         }
427                                 }
428                         }
429                 }
430
431
432                 bool ProfileExists(string username)
433                 {
434                         using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
435                         {
436                                 using (SqliteCommand dbCommand = dbConn.CreateCommand())
437                                 {
438                                         dbCommand.CommandText = string.Format("SELECT COUNT(*) FROM \"{0}\" WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName", m_ProfilesTableName);
439
440                                         AddParameter (dbCommand, "@Username", username);
441                                         AddParameter (dbCommand, "@ApplicationName", m_ApplicationName);
442
443                                         try
444                                         {
445                                                 dbConn.Open();
446                                                 dbCommand.Prepare();
447
448                                                 int numRecs = 0;
449                                                 Int32.TryParse(dbCommand.ExecuteScalar().ToString(), out numRecs);
450
451                                                 if (numRecs > 0)
452                                                         return true;
453                                         }
454                                         catch (SqliteException e)
455                                         {
456                                                 Trace.WriteLine(e.ToString());
457                                                 throw new ProviderException(Properties.Resources.ErrOperationAborted);
458                                         }
459                                         finally
460                                         {
461                                                 if (dbConn != null)
462                                                         dbConn.Close();
463                                         }
464                                 }
465                         }
466
467                         return false;
468                 }
469
470                 /// <summary>
471                 /// Updates the LastActivityDate and LastUpdatedDate values when profile properties are accessed by the
472                 /// GetPropertyValues and SetPropertyValues methods.
473                 /// Passing true as the activityOnly parameter will update only the LastActivityDate.
474                 /// </summary>
475                 /// <param name="username"></param>
476                 /// <param name="isAuthenticated"></param>
477                 /// <param name="activityOnly"></param>
478                 void UpdateActivityDates(string username, bool isAuthenticated, bool activityOnly)
479                 {
480                         using (SqliteConnection dbConn = new SqliteConnection(m_ConnectionString))
481                         {
482                                 using (SqliteCommand dbCommand = dbConn.CreateCommand())
483                                 {
484                                         if (activityOnly)
485                                         {
486                                                 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"LastActivityDate\" = @LastActivityDate WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName AND \"IsAnonymous\" = @IsAuthenticated", m_ProfilesTableName);
487
488                                                 AddParameter (dbCommand, "@LastActivityDate", DateTime.Now);
489                                         }
490                                         else
491                                         {
492                                                 dbCommand.CommandText = string.Format("UPDATE \"{0}\" SET \"LastActivityDate\" = @LastActivityDate, \"LastUpdatedDate\" = @LastUpdatedDate WHERE \"Username\" = @Username AND \"ApplicationName\" = @ApplicationName AND \"IsAnonymous\" = @IsAuthenticated", m_ProfilesTableName);
493
494                                                 AddParameter (dbCommand, "@LastActivityDate", DateTime.Now);
495                                                 AddParameter (dbCommand, "@LastUpdatedDate", DateTime.Now);
496                                         }
497                                         
498                                         AddParameter (dbCommand, "@Username", username);
499                                         AddParameter (dbCommand, "@ApplicationName", m_ApplicationName);
500                                         AddParameter (dbCommand, "@IsAuthenticated", !isAuthenticated);
501
502                                         try
503                                         {
504                                                 dbConn.Open();
505                                                 dbCommand.Prepare();
506
507                                                 dbCommand.ExecuteNonQuery();
508                                         }
509                                         catch (SqliteException e)
510                                         {
511                                                 Trace.WriteLine(e.ToString());
512                                                 throw new ProviderException(Properties.Resources.ErrOperationAborted);
513                                         }
514                                         finally
515                                         {
516                                                 if (dbConn != null)
517                                                         dbConn.Close();
518                                         }
519                                 }
520                         }
521                 }
522
523                 /// <summary>
524                 /// A helper function to retrieve config values from the configuration file.
525                 /// </summary>
526                 /// <param name="configValue"></param>
527                 /// <param name="defaultValue"></param>
528                 /// <returns></returns>
529                 string GetConfigValue(string configValue, string defaultValue)
530                 {
531                         if (string.IsNullOrEmpty(configValue))
532                                 return defaultValue;
533
534                         return configValue;
535                 }
536                 #endregion
537         }
538 }
539 #endif