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