Merge branch 'master' of http://github.com/mono/mono
[mono.git] / mcs / class / Mainsoft.Web / Mainsoft.Web.Profile / DerbyProfileProvider.cs
1 //\r
2 // Mainsoft.Web.Profile.DerbyProfileProvider\r
3 //\r
4 // Authors:\r
5 //      Vladimir Krasnov (vladimirk@mainsoft.com)\r
6 //\r
7 // (C) 2006 Mainsoft\r
8 //\r
9 // Permission is hereby granted, free of charge, to any person obtaining\r
10 // a copy of this software and associated documentation files (the\r
11 // "Software"), to deal in the Software without restriction, including\r
12 // without limitation the rights to use, copy, modify, merge, publish,\r
13 // distribute, sublicense, and/or sell copies of the Software, and to\r
14 // permit persons to whom the Software is furnished to do so, subject to\r
15 // the following conditions:\r
16 // \r
17 // The above copyright notice and this permission notice shall be\r
18 // included in all copies or substantial portions of the Software.\r
19 // \r
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
27 //\r
28 \r
29 #if NET_2_0\r
30 \r
31 using System;\r
32 using System.Data.OleDb;\r
33 using System.Data.Common;\r
34 using System.Collections;\r
35 using System.Configuration;\r
36 using System.Globalization;\r
37 using System.Web.Profile;\r
38 using System.Web.Configuration;\r
39 using System.Collections.Specialized;\r
40 using System.Text;\r
41 using System.IO;\r
42 \r
43 using Mainsoft.Web.Security;\r
44 using System.Configuration.Provider;\r
45 \r
46 namespace Mainsoft.Web.Profile\r
47 {\r
48         /// <summary>\r
49         /// <para>This class supports the Framework infrastructure and is not intended to be used directly from your code.</para>\r
50         /// <para>Manages storage of profile information for an ASP.NET application in a Derby database.</para>\r
51         /// </summary>\r
52         public class DerbyProfileProvider : ProfileProvider\r
53         {\r
54                 ConnectionStringSettings _connectionString;\r
55                 string _applicationName = string.Empty;\r
56                 bool _schemaChecked = false;\r
57                 DerbyUnloadManager.DerbyShutDownPolicy _shutDownPolicy = DerbyUnloadManager.DerbyShutDownPolicy.Default;\r
58 \r
59                 public DerbyProfileProvider ()\r
60                 {\r
61                 }\r
62 \r
63                 public override string ApplicationName\r
64                 {\r
65                         get { return _applicationName; }\r
66                         set { _applicationName = value; }\r
67                 }\r
68 \r
69                 DbConnection CreateConnection ()\r
70                 {\r
71                         if (!_schemaChecked) {\r
72                                 DerbyDBSchema.CheckSchema (_connectionString.ConnectionString);\r
73                                 _schemaChecked = true;\r
74 \r
75                                 DerbyUnloadManager.RegisterUnloadHandler (_connectionString.ConnectionString, _shutDownPolicy);\r
76                         }\r
77 \r
78                         OleDbConnection connection = new OleDbConnection (_connectionString.ConnectionString);\r
79                         connection.Open ();\r
80                         return connection;\r
81                 }\r
82                 \r
83                 public override int DeleteInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)\r
84                 {\r
85                         using (DbConnection connection = CreateConnection ()) {\r
86                                 return DerbyProfileHelper.Profile_DeleteInactiveProfiles (connection, ApplicationName, (int) authenticationOption, userInactiveSinceDate);\r
87                         }\r
88                 }\r
89 \r
90                 public override int DeleteProfiles (ProfileInfoCollection profiles)\r
91                 {\r
92                         if (profiles == null)\r
93                                 throw new ArgumentNullException ("profiles");\r
94                         if (profiles.Count == 0)\r
95                                 throw new ArgumentException ("profiles");\r
96 \r
97                         string [] usernames = new string [profiles.Count];\r
98 \r
99                         int i = 0;\r
100                         foreach (ProfileInfo pi in profiles) {\r
101                                 if (pi.UserName == null)\r
102                                         throw new ArgumentNullException ("element in profiles collection is null");\r
103 \r
104                                 if (pi.UserName.Length == 0 || pi.UserName.Length > 256 || pi.UserName.IndexOf (",") != -1)\r
105                                         throw new ArgumentException ("element in profiles collection in illegal format");\r
106 \r
107                                 usernames [i++] = pi.UserName;\r
108                         }\r
109 \r
110                         return DeleteProfilesInternal (usernames);\r
111                 }\r
112 \r
113                 public override int DeleteProfiles (string [] usernames)\r
114                 {\r
115                         if (usernames == null)\r
116                                 throw new ArgumentNullException ("usernames");\r
117 \r
118                         Hashtable users = new Hashtable ();\r
119                         foreach (string username in usernames) {\r
120                                 if (username == null)\r
121                                         throw new ArgumentNullException ("element in usernames array is null");\r
122 \r
123                                 if (username.Length == 0 || username.Length > 256 || username.IndexOf (",") != -1)\r
124                                         throw new ArgumentException ("element in usernames array in illegal format");\r
125 \r
126                                 if (users.ContainsKey (username))\r
127                                         throw new ArgumentException ("duplicate element in usernames array");\r
128 \r
129                                 users.Add (username, username);\r
130                         }\r
131 \r
132                         return DeleteProfilesInternal (usernames);\r
133                 }\r
134 \r
135                 private int DeleteProfilesInternal (string[] usernames)\r
136                 {\r
137                         using (DbConnection connection = CreateConnection ()) {\r
138                                 return DerbyProfileHelper.Profile_DeleteProfiles (connection, ApplicationName, usernames);\r
139                         }\r
140                 }\r
141 \r
142                 public override ProfileInfoCollection FindInactiveProfilesByUserName (ProfileAuthenticationOption authenticationOption,\r
143                                                                                           string usernameToMatch,\r
144                                                                                           DateTime userInactiveSinceDate,\r
145                                                                                           int pageIndex,\r
146                                                                                           int pageSize,\r
147                                                                                           out int totalRecords)\r
148                 {\r
149                         CheckParam ("usernameToMatch", usernameToMatch, 256);\r
150                         if (pageIndex < 0)\r
151                                 throw new ArgumentException ("pageIndex is less than zero");\r
152                         if (pageSize < 1)\r
153                                 throw new ArgumentException ("pageIndex is less than one");\r
154                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)\r
155                                 throw new ArgumentException ("pageIndex and pageSize are too large");\r
156 \r
157                         DbDataReader reader = null;\r
158                         using (DbConnection connection = CreateConnection ()) {\r
159                                 totalRecords = DerbyProfileHelper.Profile_GetInactiveProfiles (connection, ApplicationName, (int) authenticationOption, pageIndex, pageSize, usernameToMatch, userInactiveSinceDate, out reader);\r
160 \r
161                                 using (reader) {\r
162                                         return BuildProfileInfoCollection (reader, pageIndex, pageSize, out totalRecords);\r
163                                 }\r
164                         }\r
165                 }\r
166 \r
167                 public override ProfileInfoCollection FindProfilesByUserName (ProfileAuthenticationOption authenticationOption,\r
168                                                                                           string usernameToMatch,\r
169                                                                                           int pageIndex,\r
170                                                                                           int pageSize,\r
171                                                                                           out int totalRecords)\r
172                 {\r
173                         CheckParam ("usernameToMatch", usernameToMatch, 256);\r
174                         if (pageIndex < 0)\r
175                                 throw new ArgumentException ("pageIndex is less than zero");\r
176                         if (pageSize < 1)\r
177                                 throw new ArgumentException ("pageIndex is less than one");\r
178                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)\r
179                                 throw new ArgumentException ("pageIndex and pageSize are too large");\r
180 \r
181                         DbDataReader reader = null;\r
182                         using (DbConnection connection = CreateConnection ()) {\r
183                                 totalRecords = DerbyProfileHelper.Profile_GetProfiles (connection, ApplicationName, (int) authenticationOption, pageIndex, pageSize, usernameToMatch, out reader);\r
184 \r
185                                 using (reader) {\r
186                                         return BuildProfileInfoCollection (reader, pageIndex, pageSize, out totalRecords);\r
187                                 }\r
188                         }\r
189                 }\r
190 \r
191                 public override ProfileInfoCollection GetAllInactiveProfiles (ProfileAuthenticationOption authenticationOption,\r
192                                                                                       DateTime userInactiveSinceDate,\r
193                                                                                       int pageIndex,\r
194                                                                                       int pageSize,\r
195                                                                                       out int totalRecords)\r
196                 {\r
197                         if (pageIndex < 0)\r
198                                 throw new ArgumentException ("pageIndex is less than zero");\r
199                         if (pageSize < 1)\r
200                                 throw new ArgumentException ("pageIndex is less than one");\r
201                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)\r
202                                 throw new ArgumentException ("pageIndex and pageSize are too large");\r
203 \r
204                         DbDataReader reader = null;\r
205                         using (DbConnection connection = CreateConnection ()) {\r
206                                 totalRecords = DerbyProfileHelper.Profile_GetInactiveProfiles (\r
207                                         connection, ApplicationName, (int) authenticationOption, \r
208                                         pageIndex, pageSize, null, userInactiveSinceDate, out reader);\r
209 \r
210                                 using (reader) {\r
211                                         return BuildProfileInfoCollection (reader, pageIndex, pageSize, out totalRecords);\r
212                                 }\r
213                         }\r
214                 }\r
215 \r
216                 public override ProfileInfoCollection GetAllProfiles (ProfileAuthenticationOption authenticationOption,\r
217                                                                                       int pageIndex,\r
218                                                                                       int pageSize,\r
219                                                                                       out int totalRecords)\r
220                 {\r
221                         if (pageIndex < 0)\r
222                                 throw new ArgumentException ("pageIndex is less than zero");\r
223                         if (pageSize < 1)\r
224                                 throw new ArgumentException ("pageIndex is less than one");\r
225                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)\r
226                                 throw new ArgumentException ("pageIndex and pageSize are too large");\r
227 \r
228                         DbDataReader reader = null;\r
229                         using (DbConnection connection = CreateConnection ()) {\r
230                                 totalRecords = DerbyProfileHelper.Profile_GetProfiles (\r
231                                         connection, ApplicationName, (int) authenticationOption, \r
232                                         pageIndex, pageSize, null, out reader);\r
233 \r
234                                 using (reader) {\r
235                                         return BuildProfileInfoCollection (reader, pageIndex, pageSize, out totalRecords);\r
236                                 }\r
237                         }\r
238                 }\r
239 \r
240                 public override int GetNumberOfInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)\r
241                 {\r
242                         using (DbConnection connection = CreateConnection ()) {\r
243                                 return DerbyProfileHelper.Profile_GetNumberOfInactiveProfiles (\r
244                                         connection, ApplicationName, (int) authenticationOption, userInactiveSinceDate);\r
245                         }\r
246                 }\r
247 \r
248                 public override SettingsPropertyValueCollection GetPropertyValues (SettingsContext sc, SettingsPropertyCollection properties)\r
249                 {\r
250                         SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection ();\r
251 \r
252                         if (properties.Count == 0)\r
253                                 return settings;\r
254 \r
255                         foreach (SettingsProperty property in properties) {\r
256                                 if (property.SerializeAs == SettingsSerializeAs.ProviderSpecific)\r
257                                         if (property.PropertyType.IsPrimitive || property.PropertyType == typeof (String))\r
258                                                 property.SerializeAs = SettingsSerializeAs.String;\r
259                                         else\r
260                                                 property.SerializeAs = SettingsSerializeAs.Xml;\r
261 \r
262                                 settings.Add (new SettingsPropertyValue (property));\r
263                         }\r
264 \r
265                         string username = (string) sc ["UserName"];\r
266 \r
267                         DbDataReader reader;\r
268                         using (DbConnection connection = CreateConnection ()) {\r
269                                 DerbyProfileHelper.Profile_GetProperties (connection, ApplicationName, username, DateTime.UtcNow, out reader);\r
270                                 if (reader != null) {\r
271                                         using (reader) {\r
272                                                 if (reader.Read ()) {\r
273                                                         string allnames = reader.GetString (0);\r
274                                                         string allvalues = reader.GetString (1);\r
275                                                         int binaryLen = (int) reader.GetBytes (2, 0, null, 0, 0);\r
276                                                         byte [] binaryvalues = new byte [binaryLen];\r
277                                                         reader.GetBytes (2, 0, binaryvalues, 0, binaryLen);\r
278 \r
279                                                         DecodeProfileData (allnames, allvalues, binaryvalues, settings);\r
280                                                 }\r
281                                         }\r
282                                 }\r
283                         }\r
284                         return settings;\r
285                 }\r
286 \r
287                 public override void SetPropertyValues (SettingsContext sc, SettingsPropertyValueCollection properties)\r
288                 {\r
289                         string username = (string) sc ["UserName"];\r
290                         bool authenticated = (bool) sc ["IsAuthenticated"];\r
291 \r
292                         string names = String.Empty;\r
293                         string values = String.Empty;\r
294                         byte [] buf = null;\r
295 \r
296                         EncodeProfileData (ref names, ref values, ref buf, properties, authenticated);\r
297 \r
298                         using (DbConnection connection = CreateConnection ()) {\r
299                                         DerbyProfileHelper.Profile_SetProperties (\r
300                                         connection, _applicationName, names, values, \r
301                                         buf, username, authenticated, DateTime.UtcNow);\r
302                         }\r
303                 }\r
304 \r
305                 public override void Initialize (string name, NameValueCollection config)\r
306                 {\r
307                         if (config == null)\r
308                                 throw new ArgumentNullException ("config");\r
309 \r
310                         if (string.IsNullOrEmpty (name))\r
311                                 name = "DerbyProfileProvider";\r
312 \r
313                         if (string.IsNullOrEmpty (config ["description"])) {\r
314                                 config.Remove ("description");\r
315                                 config.Add ("description", "Derby profile provider");\r
316                         }\r
317                         base.Initialize (name, config);\r
318 \r
319                         _applicationName = GetStringConfigValue (config, "applicationName", "/");\r
320 \r
321                         ProfileSection profileSection = (ProfileSection) WebConfigurationManager.GetSection ("system.web/profile");\r
322                         string connectionStringName = config ["connectionStringName"];\r
323                         _connectionString = WebConfigurationManager.ConnectionStrings [connectionStringName];\r
324                         if (_connectionString == null)\r
325                                 throw new ProviderException (String.Format ("The connection name '{0}' was not found in the applications configuration or the connection string is empty.", connectionStringName));\r
326 \r
327                         string shutdown = config ["shutdown"];\r
328                         if (!String.IsNullOrEmpty (shutdown))\r
329                                 _shutDownPolicy = (DerbyUnloadManager.DerbyShutDownPolicy) Enum.Parse (typeof (DerbyUnloadManager.DerbyShutDownPolicy), shutdown, true);\r
330                 }\r
331 \r
332                 private ProfileInfoCollection BuildProfileInfoCollection (DbDataReader reader, int pageIndex, int pageSize, out int totalRecords)\r
333                 {\r
334                         int num_read = 0;\r
335                         int num_added = 0;\r
336                         int num_to_skip = pageIndex * pageSize;\r
337                         ProfileInfoCollection pic = new ProfileInfoCollection ();\r
338 \r
339                         while (reader.Read ()) {\r
340                                 if (num_read >= num_to_skip && num_added < pageSize) {\r
341                                         ProfileInfo pi = ReadProfileInfo (reader);\r
342                                         if (pi != null) {\r
343                                                 pic.Add (pi);\r
344                                                 num_added++;\r
345                                         }\r
346                                 }\r
347                                 num_read++;\r
348                         }\r
349                         totalRecords = num_read;\r
350                         return pic;\r
351                 }\r
352 \r
353                 private ProfileInfo ReadProfileInfo (DbDataReader reader)\r
354                 {\r
355                         string username = reader.GetString (0);\r
356                         bool anonymous = reader.GetInt32 (1) > 0;\r
357                         DateTime lastUpdate = reader.GetDateTime (2);\r
358                         DateTime lastActivity = reader.GetDateTime (3);\r
359                         int size = reader.GetInt32 (4);\r
360 \r
361                         return new ProfileInfo (username, anonymous, lastActivity, lastUpdate, size);\r
362                 }\r
363 \r
364                 // Helper methods\r
365                 private void DecodeProfileData (string allnames, string values, byte [] buf, SettingsPropertyValueCollection properties)\r
366                 {\r
367                         if (allnames == null || values == null || buf == null || properties == null)\r
368                                 return;\r
369 \r
370                         string [] names = allnames.Split (':');\r
371                         for (int i = 0; i < names.Length; i += 4) {\r
372                                 string name = names [i];\r
373                                 SettingsPropertyValue pp = properties [name];\r
374 \r
375                                 if (pp == null)\r
376                                         continue;\r
377 \r
378                                 int pos = Int32.Parse (names [i + 2], CultureInfo.InvariantCulture);\r
379                                 int len = Int32.Parse (names [i + 3], CultureInfo.InvariantCulture);\r
380 \r
381                                 if (len == -1 && !pp.Property.PropertyType.IsValueType) {\r
382                                         pp.PropertyValue = null;\r
383                                         pp.IsDirty = false;\r
384                                         pp.Deserialized = true;\r
385                                 }\r
386                                 else if (names [i + 1] == "S" && pos >= 0 && len > 0 && values.Length >= pos + len) {\r
387                                         pp.SerializedValue = values.Substring (pos, len);\r
388                                 }\r
389                                 else if (names [i + 1] == "B" && pos >= 0 && len > 0 && buf.Length >= pos + len) {\r
390                                         byte [] buf2 = new byte [len];\r
391                                         Buffer.BlockCopy (buf, pos, buf2, 0, len);\r
392                                         pp.SerializedValue = buf2;\r
393                                 }\r
394                         }\r
395                 }\r
396 \r
397                 private void EncodeProfileData (ref string allNames, ref string allValues, ref byte [] buf, SettingsPropertyValueCollection properties, bool userIsAuthenticated)\r
398                 {\r
399                         StringBuilder names = new StringBuilder ();\r
400                         StringBuilder values = new StringBuilder ();\r
401                         MemoryStream stream = new MemoryStream ();\r
402 \r
403                         try {\r
404                                 foreach (SettingsPropertyValue pp in properties) {\r
405                                         if (!userIsAuthenticated && !(bool) pp.Property.Attributes ["AllowAnonymous"])\r
406                                                 continue;\r
407 \r
408                                         if (!pp.IsDirty && pp.UsingDefaultValue)\r
409                                                 continue;\r
410 \r
411                                         int len = 0, pos = 0;\r
412                                         string propValue = null;\r
413 \r
414                                         if (pp.Deserialized && pp.PropertyValue == null)\r
415                                                 len = -1;\r
416                                         else {\r
417                                                 object sVal = pp.SerializedValue;\r
418 \r
419                                                 if (sVal == null)\r
420                                                         len = -1;\r
421                                                 else if (sVal is string) {\r
422                                                         propValue = (string) sVal;\r
423                                                         len = propValue.Length;\r
424                                                         pos = values.Length;\r
425                                                 }\r
426                                                 else {\r
427                                                         byte [] b2 = (byte []) sVal;\r
428                                                         pos = (int) stream.Position;\r
429                                                         stream.Write (b2, 0, b2.Length);\r
430                                                         stream.Position = pos + b2.Length;\r
431                                                         len = b2.Length;\r
432                                                 }\r
433                                         }\r
434 \r
435                                         names.Append (pp.Name + ":" + ((propValue != null) ? "S" : "B") + ":" + pos.ToString (CultureInfo.InvariantCulture) + ":" + len.ToString (CultureInfo.InvariantCulture) + ":");\r
436 \r
437                                         if (propValue != null)\r
438                                                 values.Append (propValue);\r
439                                 }\r
440 \r
441                                 buf = stream.ToArray ();\r
442                         }\r
443                         finally {\r
444                                 if (stream != null)\r
445                                         stream.Close ();\r
446                         }\r
447 \r
448                         allNames = names.ToString ();\r
449                         allValues = values.ToString ();\r
450                 }\r
451 \r
452                 string GetStringConfigValue (NameValueCollection config, string name, string def)\r
453                 {\r
454                         string rv = def;\r
455                         string val = config [name];\r
456                         if (val != null)\r
457                                 rv = val;\r
458                         return rv;\r
459                 }\r
460 \r
461                 void CheckParam (string pName, string p, int length)\r
462                 {\r
463                         if (p == null)\r
464                                 throw new ArgumentNullException (pName);\r
465                         if (p.Length == 0 || p.Length > length || p.IndexOf (",") != -1)\r
466                                 throw new ArgumentException (String.Format ("invalid format for {0}", pName));\r
467                 }\r
468                 \r
469 \r
470         }\r
471 }\r
472 \r
473 #endif\r