diff --git a/dotnet/framework/.gitignore b/dotnet/framework/.gitignore new file mode 100644 index 00000000..76d0819f --- /dev/null +++ b/dotnet/framework/.gitignore @@ -0,0 +1,185 @@ +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# OS generated files # +.DS_Store* +ehthumbs.db +Icon? +Thumbs.db + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings +modulesbin/ +tempbin/ + +# EPiServer Site file (VPP) +AppData/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# vim +*.txt~ +*.swp +*.swo + +# svn +.svn + +# SQL Server files +**/App_Data/*.mdf +**/App_Data/*.ldf +**/App_Data/*.sdf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store + +# SASS Compiler cache +.sass-cache + +# Visual Studio 2014 CTP +**/*.sln.ide + +# OpenICF +**/version.txt +**/AssemblyInfo.cs +Dist/ +FrameworkProtoBuf/*.cs diff --git a/dotnet/framework/BooScriptExecutorFactory/BooScriptExecutorFactory.cs b/dotnet/framework/BooScriptExecutorFactory/BooScriptExecutorFactory.cs new file mode 100644 index 00000000..eb34b10d --- /dev/null +++ b/dotnet/framework/BooScriptExecutorFactory/BooScriptExecutorFactory.cs @@ -0,0 +1,89 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ + +using System.Reflection; +using System.Collections.Generic; +using Boo.Lang.Interpreter; +using Boo.Lang.Compiler; + +namespace Org.IdentityConnectors.Common.Script.Boo +{ + [ScriptExecutorFactoryClass("Boo")] + public class BooScriptExecutorFactory : ScriptExecutorFactory + { + /// + /// Attempt to trigger an exception if the runtime is not present. + /// + public BooScriptExecutorFactory() + { + new BooScriptExecutor(new Assembly[0], "1").Execute(null); + } + + /// + /// Creates a script executor give the Boo script. + /// + override + public ScriptExecutor NewScriptExecutor(Assembly[] referencedAssemblies, string script, bool compile) + { + return new BooScriptExecutor(referencedAssemblies, script); + } + + /// + /// Processes the script. + /// + class BooScriptExecutor : ScriptExecutor + { + private readonly Assembly[] _referencedAssemblies; + private readonly string _script; + private readonly InteractiveInterpreter _engine; + + public BooScriptExecutor(Assembly[] referencedAssemblies, string script) + { + _referencedAssemblies = referencedAssemblies; + _script = script; + _engine = new InteractiveInterpreter(); + _engine.RememberLastValue = true; + foreach (Assembly assembly in referencedAssemblies) + { + _engine.References.Add(assembly); + } + } + public object Execute(IDictionary arguments) + { + // add all the globals + IDictionary args = CollectionUtil.NullAsEmpty(arguments); + foreach (KeyValuePair entry in args) + { + _engine.SetValue(entry.Key, entry.Value); + } + CompilerContext context = _engine.Eval(_script); + if (context.Errors.Count > 0) + { + throw context.Errors[0]; + } + return _engine.LastValue; + } + } + } +} diff --git a/dotnet/framework/BooScriptExecutorFactory/BooScriptExecutorFactory.csproj b/dotnet/framework/BooScriptExecutorFactory/BooScriptExecutorFactory.csproj new file mode 100644 index 00000000..d7bddd6f --- /dev/null +++ b/dotnet/framework/BooScriptExecutorFactory/BooScriptExecutorFactory.csproj @@ -0,0 +1,100 @@ + + + + + {0747C440-70E4-4E63-9F9D-03B3A010C991} + Debug + AnyCPU + Library + Org.IdentityConnectors.Common.Script.Boo + Boo.ScriptExecutorFactory + Boo ScriptExecutor Factory + v4.5.2 + + + + prompt + 4 + AnyCPU + bin\Debug\ + True + Full + False + True + DEBUG;TRACE + false + + + pdbonly + bin\Release\ + TRACE + prompt + 4 + AnyCPU + true + True + False + false + + + + lib\Boo.Lang.dll + + + lib\Boo.Lang.Compiler.dll + + + lib\Boo.Lang.Interpreter.dll + + + lib\Boo.Lang.Parser.dll + + + lib\Boo.Lang.Useful.dll + + + + 4.0 + + + + 4.0 + + + + + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Compiler.dll b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Compiler.dll new file mode 100644 index 00000000..7a4bc09b Binary files /dev/null and b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Compiler.dll differ diff --git a/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Interpreter.dll b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Interpreter.dll new file mode 100644 index 00000000..407d0114 Binary files /dev/null and b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Interpreter.dll differ diff --git a/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Parser.dll b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Parser.dll new file mode 100644 index 00000000..8bf0313d Binary files /dev/null and b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Parser.dll differ diff --git a/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Useful.dll b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Useful.dll new file mode 100644 index 00000000..73b49455 Binary files /dev/null and b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.Useful.dll differ diff --git a/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.dll b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.dll new file mode 100644 index 00000000..83dc5fbc Binary files /dev/null and b/dotnet/framework/BooScriptExecutorFactory/lib/Boo.Lang.dll differ diff --git a/dotnet/framework/BooScriptExecutorFactory/version.template b/dotnet/framework/BooScriptExecutorFactory/version.template new file mode 100644 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/BooScriptExecutorFactory/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/Common/Assertions.cs b/dotnet/framework/Common/Assertions.cs new file mode 100644 index 00000000..b9785f2a --- /dev/null +++ b/dotnet/framework/Common/Assertions.cs @@ -0,0 +1,114 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Diagnostics; + +namespace Org.IdentityConnectors.Common +{ + /// + /// Description of Assertions. + /// + public static class Assertions + { + private const string NULL_FORMAT = "Parameter '{0}' must not be null."; + private const string BLANK_FORMAT = "Parameter '{0}' must not be blank."; + + /// + /// Throws if the parameter + /// is null. + /// + /// check if the object is null. + /// name of the parameter to check for null. + /// if is null and constructs a + /// message with the name of the parameter. + public static void NullCheck(Object o, String param) + { + Debug.Assert(StringUtil.IsNotBlank(param)); + if (o == null) + { + throw new ArgumentNullException(String.Format(NULL_FORMAT, param)); + } + } + + /// + /// Throws if the parameter + /// is null, otherwise returns its value. + /// + /// the type of the parameter to check for null. Must be a reference type. + /// check if the object is null. + /// name of the parameter to check for null. + /// the value of the parameter . + /// if is null and constructs a + /// message with the name of the parameter. + public static T NullChecked(T o, String param) where T : class + { + // Avoid calling NullCheck() here to reuse code: it deepens the stack trace. + // We want the exception to be thrown as close to the call site as possible. + Debug.Assert(StringUtil.IsNotBlank(param)); + if (o == null) + { + throw new ArgumentNullException(String.Format(NULL_FORMAT, param)); + } + return o; + } + + /// + /// Throws if the parameter + /// is null or blank. + /// + /// value to test for blank. + /// name of the parameter to check. + /// if is null or blank and constructs a + /// message with the name of the parameter. + public static void BlankCheck(String o, String param) + { + Debug.Assert(StringUtil.IsNotBlank(param)); + if (StringUtil.IsBlank(o)) + { + throw new ArgumentException(String.Format(BLANK_FORMAT, param)); + } + } + + /// + /// Throws if the parameter + /// is null or blank, otherwise returns its value. + /// + /// value to test for blank. + /// name of the parameter to check. + /// the value of the parameter . + /// if is null or blank and constructs a + /// message with the name of the parameter. + public static String BlankChecked(String o, String param) + { + // Avoid calling BlankCheck() here to reuse code: it deepens the stack trace. + // We want the exception to be thrown as close to the call site as possible. + Debug.Assert(StringUtil.IsNotBlank(param)); + if (StringUtil.IsBlank(o)) + { + throw new ArgumentException(String.Format(BLANK_FORMAT, param)); + } + return o; + } + } +} diff --git a/dotnet/framework/Common/AsyncHandler.cs b/dotnet/framework/Common/AsyncHandler.cs new file mode 100755 index 00000000..397dfc9a --- /dev/null +++ b/dotnet/framework/Common/AsyncHandler.cs @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +namespace Org.IdentityConnectors.Common +{ + /// + /// A completion handler for consuming errors which occur during the execution of + /// asynchronous tasks. + /// + /// + /// The type of error consumed by the handler. + public interface IFailureHandler + { + /// + /// Invoked when the asynchronous task has failed. + /// + /// + /// The error indicating why the asynchronous task has failed. + void HandleError(TE error); + } + + /// + /// A completion handler for consuming the results of asynchronous tasks. + /// + /// + /// The type of result consumed by the handler. + public interface ISuccessHandler + { + /// + /// Invoked when the asynchronous task has completed successfully. + /// + /// + /// The result of the asynchronous task. + void HandleResult(TV result); + } + +} \ No newline at end of file diff --git a/dotnet/framework/Common/CollectionUtil.cs b/dotnet/framework/Common/CollectionUtil.cs new file mode 100644 index 00000000..941d973a --- /dev/null +++ b/dotnet/framework/Common/CollectionUtil.cs @@ -0,0 +1,1185 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2015 ForgeRock AS. + */ +using System; +using System.Runtime.CompilerServices; +using System.Reflection; +using System.Collections.Generic; +using System.Text; + +namespace Org.IdentityConnectors.Common +{ + internal class IdentityEqualityComparer : IEqualityComparer where T : class + { + public bool Equals(T x, T y) + { + return Object.ReferenceEquals(x, y); + } + public int GetHashCode(T o) + { + return RuntimeHelpers.GetHashCode(o); + } + } + + internal class ReadOnlyCollection : ICollection + { + private readonly ICollection _target; + public ReadOnlyCollection(ICollection target) + { + _target = target; + } + public void Add(T item) + { + throw new NotSupportedException(); + } + public void Clear() + { + throw new NotSupportedException(); + } + public bool Contains(T item) + { + return _target.Contains(item); + } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return _target.GetEnumerator(); + } + public IEnumerator GetEnumerator() + { + return _target.GetEnumerator(); + } + public bool IsReadOnly + { + get + { + return true; + } + } + public int Count + { + get + { + return _target.Count; + } + } + public bool Remove(T item) + { + throw new NotSupportedException(); + } + public void CopyTo(T[] array, + int arrayIndex) + { + _target.CopyTo(array, arrayIndex); + } + + public ICollection GetTarget() + { + return _target; + } + + } + + + internal class ReadOnlyList : ReadOnlyCollection, IList + { + + public ReadOnlyList(IList target) + : base(target) + { + + } + public int IndexOf(T item) + { + return GetTarget().IndexOf(item); + } + public void Insert(int index, T item) + { + throw new NotSupportedException(); + } + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + public T this[int index] + { + get + { + return GetTarget()[index]; + } + set + { + throw new NotSupportedException(); + } + } + + protected new IList GetTarget() + { + return (IList)base.GetTarget(); + } + } + + internal class ReadOnlyDictionary : + ReadOnlyCollection>, + IDictionary + { + public ReadOnlyDictionary(IDictionary target) + : base(target) + { + + } + public void Add(TKey key, TValue val) + { + throw new NotSupportedException(); + } + protected new IDictionary GetTarget() + { + return (IDictionary)base.GetTarget(); + } + public ICollection Keys + { + get + { + return new ReadOnlyCollection(GetTarget().Keys); + } + } + public ICollection Values + { + get + { + return new ReadOnlyCollection(GetTarget().Values); + } + } + public bool Remove(TKey key) + { + throw new NotSupportedException(); + } + public bool ContainsKey(TKey key) + { + return GetTarget().ContainsKey(key); + } + public bool TryGetValue(TKey key, out TValue value) + { + return GetTarget().TryGetValue(key, out value); + } + public TValue this[TKey key] + { + get + { + return GetTarget()[key]; + } + set + { + throw new NotSupportedException(); + } + } + } + + + + + /// + /// Delegate that returns the Key, given a value + /// + public delegate TKey KeyFunction(TValue value); + + + /// + /// Description of CollectionUtil. + /// + public static class CollectionUtil + { + /// + /// Creates a case-insensitive set + /// + /// An empty case-insensitive set + public static ICollection NewCaseInsensitiveSet() + { + HashSet rv = new HashSet(StringComparer.OrdinalIgnoreCase); + return rv; + } + + /// + /// Returns true if the collection is a case-insensitive set + /// + /// The collection. May be null. + /// true if the collection is a case-insensitive set + public static bool IsCaseInsensitiveSet(ICollection collection) + { + if (collection is ReadOnlyCollection) + { + ReadOnlyCollection roc = + (ReadOnlyCollection)collection; + return IsCaseInsensitiveSet(roc.GetTarget()); + } + else if (collection is HashSet) + { + HashSet set = (HashSet)collection; + return StringComparer.OrdinalIgnoreCase.Equals(set.Comparer); + } + else + { + return false; + } + } + /// + /// Creates a case-insensitive map + /// + /// An empty case-insensitive map + public static IDictionary NewCaseInsensitiveDictionary() + { + Dictionary rv = new Dictionary(StringComparer.OrdinalIgnoreCase); + return rv; + } + + /// + /// Returns true if the collection is a case-insensitive map + /// + /// The map. May be null. + /// true if the collection is a case-insensitive map + public static bool IsCaseInsensitiveDictionary(IDictionary map) + { + if (map is ReadOnlyDictionary) + { + ReadOnlyDictionary roc = + (ReadOnlyDictionary)map; + return IsCaseInsensitiveDictionary((IDictionary)roc.GetTarget()); + } + else if (map is Dictionary) + { + Dictionary d = (Dictionary)map; + return StringComparer.OrdinalIgnoreCase.Equals(d.Comparer); + } + else + { + return false; + } + } + + /// + /// Creates a dictionary where the keys are looked up using + /// reference equality + /// + /// + public static IDictionary NewIdentityDictionary() + where K : class + { + IdentityEqualityComparer comp = new IdentityEqualityComparer(); + return new Dictionary(comp); + } + + /// + /// Computes a hashCode over the enumeration. The hashCode is computed + /// such that it doesn't matter what order the elements are listed in. Thus, it + /// is suitable for arrays, lists, sets, and dictionaries + /// + /// The enumerable + /// The hashcode + public static int GetEnumerableHashCode(IEnumerable enum1) + { + if (enum1 == null) + { + return 0; + } + int rv = 0; + foreach (T val1 in enum1) + { + unchecked + { + rv += CollectionUtil.GetHashCode(val1); + } + } + return rv; + } + + /// + /// Computes a hashCode for a key value pair. + /// + /// The pair + /// The hashcode + public static int GetKeyValuePairHashCode(KeyValuePair pair) + { + int rv = 0; + unchecked + { + rv += CollectionUtil.GetHashCode(pair.Key); + rv += CollectionUtil.GetHashCode(pair.Value); + } + return rv; + } + + /// + /// Returns true if the two sets contain the same elements. This is only for + /// sets and dictionaries. Does not work for Lists or Arrays. + /// + /// The first collection + /// The second collection + /// + public static bool SetsEqual(ICollection collection1, + ICollection collection2) + { + return SetsEqual(collection1, collection2, false); + } + + private static bool _SetsEqual(ICollection collection1, + ICollection collection2, Boolean equalsIgnoreCase) + { + return SetsEqual(collection1, collection2, equalsIgnoreCase); + } + + /// + /// Returns true if the two sets contain the same elements. This is only for + /// sets and dictionaries. Does not work for Lists or Arrays. + /// + /// The first collection + /// The second collection + /// + /// + public static bool SetsEqual(ICollection collection1, + ICollection collection2, Boolean equalsIgnoreCase) + { + if (collection1 == null || collection2 == null) + { + return collection1 == null && collection2== null; + } + if (collection1.Count != collection2.Count) + { + return false; + } + foreach (T val1 in collection1) + { + if (!collection2.Contains(val1)) + { + return false; + } + } + return true; + } + + /// + /// Gets the given value from the map or default if not exists. Always + /// use this rather than map[] since map[] throws an exception if not + /// exists. + /// + /// The map + /// The key + /// The default value + /// + public static TValue GetValue(IDictionary map, + TKey key, + TValue def) + { + TValue rv; + bool present = map.TryGetValue(key, out rv); + if (present) + { + return rv; + } + else + { + return def; + } + } + + /// + /// Adds all the elements from the given enumerable to the given collection. + /// + /// The collection to add to + /// The enumerable to get from + public static void AddAll(ICollection collection, + IEnumerable enumerable) + where U : T + { + if (enumerable != null) + { + foreach (U obj in enumerable) + { + collection.Add(obj); + } + } + } + + /// + /// Adds all the elements from the given enumerable to the given collection. + /// Replace the element value if already stored in the collection. + /// + /// IDictionary key type + /// IDictionary value type + /// Enumeration key type, has to extend IDictionary key type + /// Enumeration value type, has to extend IDictionary value type + /// The collection to add to + /// The enumerable to get from + public static void AddOrReplaceAll(IDictionary collection, + IEnumerable> enumerable) + where UKey : TKey + where UValue : TValue + { + if (enumerable != null) + { + foreach (KeyValuePair obj in enumerable) + { + collection[obj.Key] = obj.Value; + } + } + } + + /// + /// Adds all the elements from the given enumerable to the given collection. + /// + /// The collection to add to + /// The enumerable to get from + public static void RemoveAll(ICollection collection, + IEnumerable enumerable) + where U : T + { + if (enumerable != null) + { + foreach (U obj in enumerable) + { + collection.Remove(obj); + } + } + } + + /// + /// Adds all the elements from the given enumerable to the given collection. + /// + /// The collection to add to + /// The enumerable to get from + public static void RemovalAll(ICollection collection, + IEnumerable enumerable) + where U : T + { + if (enumerable != null) + { + foreach (U obj in enumerable) + { + collection.Remove(obj); + } + } + } + + + /// + /// Returns c or an empty collection if c is null. + /// + /// The collection + /// c or an empty collection if c is null. + public static ICollection NullAsEmpty(ICollection c) + { + return c ?? new HashSet(); + } + + /// + /// Returns c or an empty collection if c is null. + /// + /// The collection + /// c or an empty collection if c is null. + public static IDictionary NullAsEmpty(IDictionary c) + { + return c ?? new Dictionary(); + } + + /// + /// Returns c or an empty collection if c is null. + /// + /// The collection + /// c or an empty collection if c is null. + public static IList NullAsEmpty(IList c) + { + return c ?? new List(); + } + + /// + /// Returns c or an empty array if c is null. + /// + /// The array + /// c or an empty collection if c is null. + public static T[] NullAsEmpty(T[] c) + { + return c ?? new T[0]; + } + + /// + /// Given a collection of values a key function, builds a dictionary + /// + /// List of values + /// Key function, mapping from key to value + /// The dictionay + public static IDictionary NewDictionary( + TKey k0, + TValue v0) + { + IDictionary rv = new Dictionary(); + rv[k0] = v0; + return rv; + } + + public static IDictionary NewDictionary(T k0, K v0, T k1, K v1) + { + IDictionary map = NewDictionary(k0, v0); + map[k1] = v1; + return map; + } + + public static IDictionary NewDictionary(T k0, K v0, T k1, K v1, T k2, K v2) + { + IDictionary map = NewDictionary(k0, v0, k1, v1); + map[k2] = v2; + return map; + } + + public static IDictionary NewDictionary(T k0, K v0, T k1, K v1, T k2, K v2, T k3, K v3) + { + IDictionary map = NewDictionary(k0, v0, k1, v1, k2, v2); + map[k3] = v3; + return map; + } + + public static IDictionary NewDictionary(T k0, K v0, T k1, K v1, T k2, K v2, T k3, K v3, T k4, K v4) + { + IDictionary map = NewDictionary(k0, v0, k1, v1, k2, v2, k3, v3); + map[k4] = v4; + return map; + } + + public static IDictionary NewDictionary(T k0, K v0, T k1, K v1, T k2, K v2, T k3, K v3, T k4, K v4, T k5, K v5) + { + IDictionary map = NewDictionary(k0, v0, k1, v1, k2, v2, k3, v3, k4, v4); + map[k5] = v5; + return map; + } + + /// + /// Given a collection of values a key function, builds a dictionary + /// + /// List of values + /// Key function, mapping from key to value + /// The dictionay + public static IDictionary NewDictionary( + IEnumerable values, + KeyFunction keyFunction) + { + IDictionary rv = new Dictionary(); + if (values != null) + { + foreach (TValue value in values) + { + TKey key = keyFunction(value); + //DONT use Add - it throws exceptions if already there + rv[key] = value; + } + } + return rv; + } + + /// + /// Given a collection of values a key function, builds a dictionary + /// + /// List of values + /// Key function, mapping from key to value + /// The dictionay + public static IDictionary NewReadOnlyDictionary( + IEnumerable values, + KeyFunction keyFunction) + { + IDictionary rv = + NewDictionary(values, keyFunction); + return new ReadOnlyDictionary(rv); + } + + /// + /// Given a collection of values a key function, builds a dictionary + /// + /// List of values + /// Key function, mapping from key to value + /// The dictionay + public static IDictionary NewDictionary( + IDictionary original) + { + return NewDictionary(original); + } + + /// + /// Given a collection of values a key function, builds a dictionary + /// + /// List of values + /// Key function, mapping from key to value + /// The dictionay + public static IDictionary NewDictionary( + IDictionary original) + { + IDictionary rv = new Dictionary(); + if (original != null) + { + foreach (KeyValuePair entry in original) + { + //DONT use Add - it throws exceptions if already there + rv[(TKey2)(object)entry.Key] = (TValue2)(object)entry.Value; + } + } + return rv; + } + + /// + /// Given a collection of values a key function, builds a dictionary + /// + /// List of values + /// Key function, mapping from key to value + /// The dictionay + public static IDictionary NewReadOnlyDictionary( + IDictionary original) + { + return NewReadOnlyDictionary(original); + } + + /// + /// Given a collection of values a key function, builds a dictionary + /// + /// List of values + /// Key function, mapping from key to value + /// The dictionay + public static IDictionary NewReadOnlyDictionary( + IDictionary original) + { + IDictionary rv = NewDictionary(original); + return new ReadOnlyDictionary(rv); + } + + + /// + /// Returns a modifiable list, after first copying the collection. + /// + /// A collection. May be null. + /// a modifiable list, after first copying the collection. + public static IList NewList(IEnumerable collection) + { + return NewList(collection); + } + + /// + /// Returns a modifiable list, after first copying the collection. + /// + /// A collection. May be null. + /// a modifiable list, after first copying the collection. + public static IList NewList(IEnumerable collection) + { + IList rv = new List(); + if (collection != null) + { + foreach (T element in collection) + { + rv.Add((U)(object)element); + } + } + return rv; + } + + /// + /// Returns a modifiable set, after first copying the collection. + /// + /// A collection. May be null. + /// a modifiable set, after first copying the collection. + public static ICollection NewSet(IEnumerable collection) + { + return NewSet(collection); + } + + /// + /// Returns a modifiable set, after first copying the array. + /// + /// An array maybe null. + /// a modifiable set, after first copying the array. + public static ICollection NewSet(params T[] items) + { + return NewSet((IEnumerable)items); + } + + /// + /// Returns a modifiable set, after first copying the collection. + /// + /// A collection. May be null. + /// a modifiable set, after first copying the collection. + public static ICollection NewSet(IEnumerable collection) + { + ICollection rv = new HashSet(); + if (collection != null) + { + foreach (T element in collection) + { + rv.Add((TU)(object)element); + } + } + return rv; + } + + + /// + /// Returns an unmodifiable list, after first copying the collection. + /// + /// A collection. May be null. + /// an unmodifiable list, after first copying the collection. + public static IList NewReadOnlyList(ICollection collection) + { + return NewReadOnlyList(collection); + } + + /// + /// Returns an unmodifiable list, after first copying the collection. + /// + /// A collection. May be null. + /// an unmodifiable list, after first copying the collection. + public static IList NewReadOnlyList(ICollection collection) + { + IList list = NewList(collection); + return new ReadOnlyList(list); + } + + /// + /// Returns an unmodifiable list, after first copying the collection. + /// + /// A collection. May be null. + /// an unmodifiable list, after first copying the collection. + public static ICollection NewReadOnlySet(ICollection collection) + { + return NewReadOnlySet(collection); + } + + /// + /// Returns an unmodifiable list, after first copying the collection. + /// + /// A collection. May be null. + /// an unmodifiable list, after first copying the collection. + private static ICollection NewReadOnlySet(ICollection collection) + { + ICollection list = NewSet(collection); + return new ReadOnlyCollection(list); + } + + /// + /// Returns an unmodifiable set, backed by the original + /// + /// A collection. May be null. + /// an unmodifiable list, after first copying the collection. + public static ICollection AsReadOnlySet(ICollection collection) + { + ICollection list = NullAsEmpty(collection); + return new ReadOnlyCollection(list); + } + + /// + /// Returns an unmodifiable list, backed by the original + /// + /// A collection. May be null. + /// an unmodifiable list, after first copying the collection. + public static IList AsReadOnlyList(IList collection) + { + IList list = NullAsEmpty(collection); + return new ReadOnlyList(list); + } + + /// + /// Returns an unmodifiable list, backed by the original + /// + /// A collection. May be null. + /// an unmodifiable list, after first copying the collection. + public static IDictionary AsReadOnlyDictionary(IDictionary d) + { + d = NullAsEmpty(d); + return new ReadOnlyDictionary(d); + } + + /// + /// Creates a new read-only list from an array. + /// + /// + /// + public static IList NewReadOnlyList(params T[] args) + { + return NewReadOnlyList(args); + } + + /// + /// Creates a new read-only list from an array. + /// + /// + /// + private static IList NewReadOnlyList(params T[] args) + { + IList list = CollectionUtil.NewList(args); + return new ReadOnlyList(list); + } + + /// + /// Creates a new read-only set from an array. + /// + /// + /// + public static ICollection NewReadOnlySet(params T[] args) + { + return NewReadOnlySet(args); + } + /// + /// Creates a new read-only set from an array. + /// + /// + /// + private static ICollection NewReadOnlySet(params T[] args) + { + ICollection list = CollectionUtil.NewSet(args); + return new ReadOnlyCollection(list); + } + + public static bool DictionariesEqual(IDictionary m1, + IDictionary m2) + { + return DictionariesEqual(m1, m2, false); + } + + private static bool _DictionariesEqual(IDictionary m1, + IDictionary m2, Boolean equalsIgnoreCase) + { + return DictionariesEqual(m1, m2, equalsIgnoreCase); + } + + public static bool DictionariesEqual(IDictionary m1, + IDictionary m2, Boolean equalsIgnoreCase) + { + if (m1.Count != m2.Count) + { + return false; + } + foreach (KeyValuePair entry1 in m1) + { + TK key1 = entry1.Key; + TV val1 = entry1.Value; + if (!m2.ContainsKey(key1)) + { + return false; + } + Object val2 = m2[key1]; + if (!CollectionUtil.Equals(val1, val2, equalsIgnoreCase)) + { + return false; + } + } + return true; + } + + public static bool ListsEqual(IList v1, + IList v2) + { + return ListsEqual(v1, v2, false); + } + + private static bool _ListsEqual(IList v1, + IList v2, Boolean equalsIgnoreCase) + { + return ListsEqual(v1, v2, equalsIgnoreCase); + } + + public static bool ListsEqual(IList v1, + IList v2, Boolean equalsIgnoreCase) + { + if (v1.Count != v2.Count) + { + return false; + } + for (int i = 0; i < v1.Count; i++) + { + if (!CollectionUtil.Equals(v1[i], v2[i])) + { + return false; + } + } + return true; + } + + /// + /// Forces the compare of two comparable objects and removes any warnings + /// generated by the compiler. + /// + /// the integer value of o1.compareTo(o2). + public static int ForceCompare(object o1, object o2) + { + IComparable t1 = (IComparable)o1; + T t2 = (T)o2; + return t1.CompareTo(t2); + } + + /// + /// hashCode function that properly handles arrays, + /// collections, maps, collections of arrays, and maps of arrays. + /// + /// The object. May be null. + /// the hashCode + public static int GetHashCode(Object o) + { + if (o == null) + { + return 0; + } + else if (o is Array) + { + Array array = (Array)o; + int length = array.Length; + int rv = 0; + for (int i = 0; i < length; i++) + { + Object el = array.GetValue(i); + unchecked + { + rv += GetHashCode(el); + } + } + return rv; + } + else if (ReflectionUtil.IsParentTypeOf(typeof(KeyValuePair<,>), o.GetType())) + { + Type parent = ReflectionUtil.FindInHierarchyOf(typeof(KeyValuePair<,>), o.GetType()); + Type[] genericArguments = + parent.GetGenericArguments(); + + Type collectionUtil = typeof(CollectionUtil); + MethodInfo info = collectionUtil.GetMethod("GetKeyValuePairHashCode"); + + info = info.MakeGenericMethod(genericArguments); + + Object rv = info.Invoke(null, new object[] { o }); + return (int)rv; + } + else if (ReflectionUtil.IsParentTypeOf(typeof(ICollection<>), o.GetType())) + { + Type parent = ReflectionUtil.FindInHierarchyOf(typeof(ICollection<>), o.GetType()); + + Type[] genericArguments = + parent.GetGenericArguments(); + + + Type collectionUtil = typeof(CollectionUtil); + MethodInfo info = collectionUtil.GetMethod("GetEnumerableHashCode"); + + info = info.MakeGenericMethod(genericArguments); + + Object rv = info.Invoke(null, new object[] { o }); + return (int)rv; + } + else + { + return o.GetHashCode(); + } + } + + /// + /// Equality function that properly handles arrays, + /// lists, maps, lists of arrays, and maps of arrays. + /// + /// + /// + /// NOTE: For Sets, this relies on the equals method + /// of the Set to do the right thing. This is a reasonable + /// assumption since, in order for Sets to behave + /// properly as Sets, their values must already have + /// a proper implementation of equals. (Or they must + /// be specialized Sets that define a custom comparator that + /// knows how to do the right thing). The same holds true for Map keys. + /// Map values, on the other hand, are compared (so Map values + /// can be arrays). + /// + /// + /// The first object. May be null. + /// The second object. May be null. + /// true if the two objects are equal. + public new static bool Equals(Object o1, Object o2) + { + return Equals(o1, o2, false); + } + + + /// + /// Equality function that properly handles arrays, + /// lists, maps, lists of arrays, and maps of arrays. + /// + /// + /// + /// NOTE: For Sets, this relies on the equals method + /// of the Set to do the right thing. This is a reasonable + /// assumption since, in order for Sets to behave + /// properly as Sets, their values must already have + /// a proper implementation of equals. (Or they must + /// be specialized Sets that define a custom comparator that + /// knows how to do the right thing). The same holds true for Map keys. + /// Map values, on the other hand, are compared (so Map values + /// can be arrays). + /// + /// + /// The first object. May be null. + /// The second object. May be null. + /// If true the String and Character comparison is case-ignore + /// true if the two objects are equal. + public static bool Equals(Object o1, Object o2, Boolean equalsIgnoreCase) + { + if (o1 == o2) + { //same object or both null + return true; + } + else if (o1 == null) + { + return false; + } + else if (o2 == null) + { + return false; + } + else if (o1 is Array) + { + Type clazz1 = o1.GetType(); + Type clazz2 = o2.GetType(); + if (!clazz1.Equals(clazz2)) + { + return false; + } + Array array1 = (Array)o1; + Array array2 = (Array)o2; + int length1 = array1.Length; + int length2 = array2.Length; + if (length1 != length2) + { + return false; + } + for (int i = 0; i < length1; i++) + { + Object el1 = array1.GetValue(i); + Object el2 = array2.GetValue(i); + if (!Equals(el1, el2, equalsIgnoreCase)) + { + return false; + } + } + return true; + } + else if (ReflectionUtil.IsParentTypeOf(typeof(IList<>), o1.GetType())) + { + Type parent1 = ReflectionUtil.FindInHierarchyOf(typeof(IList<>), o1.GetType()); + Type parent2 = ReflectionUtil.FindInHierarchyOf(typeof(IList<>), o2.GetType()); + if (!parent1.Equals(parent2)) + { + return false; + } + Type[] genericArguments = + parent1.GetGenericArguments(); + + + Type collectionUtil = typeof(CollectionUtil); + MethodInfo info = collectionUtil.GetMethod("_ListsEqual", BindingFlags.Static | BindingFlags.NonPublic); + + info = info.MakeGenericMethod(genericArguments); + + Object rv = info.Invoke(null, new object[] { o1, o2, equalsIgnoreCase }); + return (bool)rv; + } + else if (ReflectionUtil.IsParentTypeOf(typeof(IDictionary<,>), o1.GetType())) + { + Type parent1 = ReflectionUtil.FindInHierarchyOf(typeof(IDictionary<,>), o1.GetType()); + Type parent2 = ReflectionUtil.FindInHierarchyOf(typeof(IDictionary<,>), o2.GetType()); + if (!parent1.Equals(parent2)) + { + return false; + } + Type[] genericArguments = + parent1.GetGenericArguments(); + + + Type collectionUtil = typeof(CollectionUtil); + MethodInfo info = collectionUtil.GetMethod("_DictionariesEqual", BindingFlags.Static | BindingFlags.NonPublic); + + info = info.MakeGenericMethod(genericArguments); + + Object rv = info.Invoke(null, new object[] { o1, o2 , equalsIgnoreCase}); + return (bool)rv; + } + else if (ReflectionUtil.IsParentTypeOf(typeof(ICollection<>), o1.GetType())) + { + Type parent1 = ReflectionUtil.FindInHierarchyOf(typeof(ICollection<>), o1.GetType()); + Type parent2 = ReflectionUtil.FindInHierarchyOf(typeof(ICollection<>), o2.GetType()); + if (!parent1.Equals(parent2)) + { + return false; + } + Type[] genericArguments = + parent1.GetGenericArguments(); + + + Type collectionUtil = typeof(CollectionUtil); + MethodInfo info = collectionUtil.GetMethod("_SetsEqual", BindingFlags.Static | BindingFlags.NonPublic); + + info = info.MakeGenericMethod(genericArguments); + + Object rv = info.Invoke(null, new object[] { o1, o2, equalsIgnoreCase }); + return (bool)rv; + } + else if (equalsIgnoreCase && o1 is string && o2 is string) + { + return ((string)o1).Equals((string)o2, StringComparison.CurrentCultureIgnoreCase); + } + else if (equalsIgnoreCase && o1 is char && o2 is char) + { + return char.ToLower((char)o1) == char.ToLower((char)o2); + } + else + { + return o1.Equals(o2); + } + } + + public static string Dump(ICollection list) + { + StringBuilder sb = new StringBuilder(); + if (list != null) + { + bool first = true; + foreach (object o in list) + { + if (first) + { + first = false; + } + else + { + sb.Append(", "); + } + sb.Append(o); + } + } + else + { + sb.Append("(null)"); + } + return sb.ToString(); + } + + + } + +} diff --git a/dotnet/framework/Common/Common.csproj b/dotnet/framework/Common/Common.csproj new file mode 100644 index 00000000..955da625 --- /dev/null +++ b/dotnet/framework/Common/Common.csproj @@ -0,0 +1,109 @@ + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Debug + AnyCPU + Library + Org.IdentityConnectors.Common + Common + Open Connectors Common + v4.5.2 + True + False + 4 + false + true + + + + bin\Debug\ + true + Full + False + True + DEBUG;TRACE + + + bin\Release\ + true + pdbonly + True + False + TRACE + + + False + Auto + 4194304 + AnyCPU + 4096 + + + false + + + false + + + + + + 4.0 + + + + 4.0 + + + + 4.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/Common/DateTimeUtil.cs b/dotnet/framework/Common/DateTimeUtil.cs new file mode 100644 index 00000000..15b934fd --- /dev/null +++ b/dotnet/framework/Common/DateTimeUtil.cs @@ -0,0 +1,54 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using System; + +namespace Org.IdentityConnectors.Common +{ + /// + /// Description of DateTimeUtil. + /// + public static class DateTimeUtil + { + private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc); + + public static DateTime GetDateTimeFromUtcMillis(long dateTime) + { + return DateTime.FromFileTimeUtc(dateTime * 10000); + } + + public static long GetUtcTimeMillis(DateTime dateTime) + { + return dateTime.ToFileTimeUtc() / 10000; + } + + public static long GetCurrentUtcTimeMillis() + { + return GetUtcTimeMillis(DateTime.Now); + } + + public static long GetCurrentUnixTimeMillis() + { + return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds; + } + } +} diff --git a/dotnet/framework/Common/FrameworkInternalBridge.cs b/dotnet/framework/Common/FrameworkInternalBridge.cs new file mode 100644 index 00000000..7f64841e --- /dev/null +++ b/dotnet/framework/Common/FrameworkInternalBridge.cs @@ -0,0 +1,58 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using System; +using System.Reflection; +namespace Org.IdentityConnectors.Common +{ + /// + /// Description of FrameworkInternalBridge. + /// + internal static class FrameworkInternalBridge + { + private static readonly Object LOCK = new Object(); + private static Assembly _assembly = null; + /// + /// Loads a class from the FrameworkInternal module + /// + /// + /// + public static Type LoadType(String typeName) + { + + Assembly assembly; + lock (LOCK) + { + if (_assembly == null) + { + AssemblyName assemName = new AssemblyName(); + assemName.Name = "FrameworkInternal"; + _assembly = Assembly.Load(assemName); + } + assembly = _assembly; + } + + return assembly.GetType(typeName, true); + + } + } +} diff --git a/dotnet/framework/Common/IOUtil.cs b/dotnet/framework/Common/IOUtil.cs new file mode 100644 index 00000000..7d35aa10 --- /dev/null +++ b/dotnet/framework/Common/IOUtil.cs @@ -0,0 +1,446 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Net; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; +using System.IO; +using System.Data.SqlClient; +using System.Reflection; + +namespace Org.IdentityConnectors.Common +{ + #region IOUtil + /// + /// Description of IOUtil. + /// + public static class IOUtil + { + /// + /// Given an ip address or host, returns the + /// IPAddress + /// + /// + /// + public static IPAddress GetIPAddress(String hostOrIp) + { + if (hostOrIp.Equals("0.0.0.0") || + hostOrIp.Equals("::0")) + { + return IPAddress.Parse(hostOrIp); + } + return Dns.GetHostAddresses(hostOrIp)[0]; + } + + /// + /// Quietly closes the reader. + ///

+ /// This avoids having to handle exceptions, and then inside of the exception + /// handling have a try catch block to close the reader and catch any + /// which may be thrown and ignore it. + ///

+ /// + /// Reader to close + public static void QuietClose(TextReader reader) + { + try + { + if (reader != null) + { + reader.Close(); + } + } + catch (IOException) + { + // ignore + } + } + + /// + /// Quietly closes the writer. + ///

+ /// This avoids having to handle exceptions, and then inside of the exception + /// handling have a try catch block to close the Writer and catch any + /// which may be thrown. + ///

+ /// + /// Writer to close + public static void QuietClose(TextWriter writer) + { + try + { + if (writer != null) + { + writer.Close(); + } + } + catch (IOException) + { + // ignore + } + } + + /// + /// Quietly closes the stream. + ///

+ /// This avoids having to handle exceptions, and then inside of the exception + /// handling have a try catch block to close the stream and catch any + /// which may be thrown. + ///

+ /// + /// Stream to close + public static void QuietClose(Stream stream) + { + try + { + if (stream != null) + { + stream.Close(); + } + } + catch (IOException) + { + // ignore + } + } + + /// + /// Quietly dispose the statement. + ///

+ /// This avoids having to handle exceptions, and then inside of the exception + /// handling have a try catch block to close the statement and catch any + /// which may be thrown. + ///

+ /// + /// Statement to dispose + /// Since 1.3 + public static void QuietClose(SqlCommand stmt) + { + try + { + if (stmt != null) + { + stmt.Dispose(); + } + } + catch (SqlException) + { + // ignore + } + } + + /// + /// Quietly closes the connection. + ///

+ /// This avoids having to handle exceptions, and then inside of the exception + /// handling have a try catch block to close the connection and catch any + /// which may be thrown. + ///

+ /// + /// Connection to close + /// Since 1.3 + public static void QuietClose(SqlConnection conn) + { + try + { + if (conn != null) + { + conn.Close(); + } + } + catch (SqlException) + { + // ignore + } + } + + /// + /// Quietly closes the resultset. + ///

+ /// This avoids having to handle exceptions, and then inside of the exception + /// handling have a try catch block to close the connection and catch any + /// which may be thrown. + ///

+ /// + /// ResultSet to close + /// Since 1.3 + public static void QuietClose(SqlDataReader resultset) + { + try + { + if (resultset != null) + { + resultset.Close(); + } + } + catch (SqlException) + { + // ignore + } + } + + // ======================================================================= + // Resource Utility Methods + // ======================================================================= + /// + /// Returns an Assembly contains the typeName. + /// + /// + /// Returns an Assembly or null if not found + public static Assembly GetAssemblyContainingType(String typeName) + { + foreach (Assembly currentassembly in AppDomain.CurrentDomain.GetAssemblies()) + { + Type t = currentassembly.GetType(typeName, false, true); + if (t != null) + { + return currentassembly; + } + } + return null; + } + + /// + /// Returns an input stream of the resource specified. + /// + /// + /// + /// Returns an InputStream to the resource. + public static Stream GetResourceAsStream(Type clazz, string resourceName) + { + Debug.Assert(clazz != null && StringUtil.IsNotBlank(resourceName)); + return clazz.Assembly.GetManifestResourceStream(resourceName); + } + + /// + /// Get the resource as a byte array. + /// + /// + /// + /// @return + public static byte[] GetResourceAsBytes(Type clazz, string res) + { + Debug.Assert(clazz != null && StringUtil.IsNotBlank(res)); + // copy bytes from the stream to an array.. + Stream ins = GetResourceAsStream(clazz, res); + if (ins == null) + { + throw new InvalidOperationException("Resource not found: " + res); + } + + byte[] buffer = new byte[16 * 1024]; + using (MemoryStream ms = new MemoryStream()) + { + int read; + while ((read = ins.Read(buffer, 0, buffer.Length)) > 0) + { + ms.Write(buffer, 0, read); + } + QuietClose(ins); + return ms.ToArray(); + } + } + + /// + /// Read the entire stream into a String and return it. + /// + /// + /// + /// + public static string GetResourceAsString(Type clazz, string res, Encoding charset) + { + Debug.Assert(clazz != null && StringUtil.IsNotBlank(res)); + string ret = null; + Stream ins = GetResourceAsStream(clazz, res); + if (ins != null) + { + using (StreamReader reader = new StreamReader(ins, charset)) + { + ret = reader.ReadToEnd(); + } + QuietClose(ins); + } + return ret; + } + + /// + /// Read the entire stream into a String and return it. + /// + /// + /// + /// + public static string GetResourceAsString(Type clazz, string res) + { + Debug.Assert(clazz != null && StringUtil.IsNotBlank(res)); + return GetResourceAsString(clazz, res, Encoding.UTF8); + } + + /// + /// Copies a file to a destination. + /// + /// + /// The source must be a file + /// + /// This can be a directory or a file. + /// True if succeeded otherwise false. + public static bool CopyFile(String src, String dest) + { + bool ret = true; + // quick exit if this is bogus + if (src == null || dest == null || !File.Exists(src)) + { + throw new FileNotFoundException(); + } + // check for directory + if (!Directory.Exists(dest)) + { + Directory.CreateDirectory(dest); + } + File.Copy(src, dest, true); + return ret; + } + + /// + /// Copies one file to another. + /// + /// NOTE: does not close streams. + /// + /// + /// + /// + /// + /// total bytes copied. + public static void CopyFile(Stream input, Stream output) + { + byte[] buffer = new byte[32768]; + int read; + while ((read = input.Read(buffer, 0, buffer.Length)) > 0) + { + output.Write(buffer, 0, read); + } + } + + /// + /// Reads the given file as UTF-8 + /// + /// + /// The file to read + /// The contents of the file + /// + /// if there is an issue reading the file. + public static string ReadFileUTF8(String file) + { + string content; + using (StreamReader reader = new StreamReader(file, Encoding.UTF8)) + { + content = reader.ReadToEnd(); + } + return content; + } + + /// + /// Write the contents of the string out to a file in UTF-8 format. + /// + /// + /// the file to write to. + /// + /// the contents of the file to write to. + /// + /// if there is an issue writing the file. + /// + /// if the file parameter is null. + public static void WriteFileUTF8(String file, string contents) + { + using (var sw = new StreamWriter(File.Open(file, FileMode.CreateNew), Encoding.UTF8)) + { + sw.WriteLine(contents); + } + } + + /// + /// + /// + /// Since 1.3 + public static string Join(ICollection collection, char separator) + { + if (collection == null) + { + return null; + } + + return Join(new List(collection).ToArray(), separator, 0, collection.Count); + } + + /// + /// + /// + /// Since 1.3 + public static string Join(object[] array, char separator) + { + if (array == null) + { + return null; + } + + return Join(array, separator, 0, array.Length); + } + + /// + /// + /// + /// + /// + /// + /// Since 1.3 + public static string Join(object[] array, char separator, int startIndex, int endIndex) + { + if (array == null) + { + return null; + } + int noOfItems = endIndex - startIndex; + if (noOfItems <= 0) + { + return String.Empty; + } + + StringBuilder buf = new StringBuilder(noOfItems * 16); + + for (int i = startIndex; i < endIndex; i++) + { + if (i > startIndex) + { + buf.Append(separator); + } + if (array[i] != null) + { + buf.Append(array[i]); + } + } + return buf.ToString(); + } + } + #endregion +} diff --git a/dotnet/framework/Common/Locale.cs b/dotnet/framework/Common/Locale.cs new file mode 100644 index 00000000..342b515e --- /dev/null +++ b/dotnet/framework/Common/Locale.cs @@ -0,0 +1,240 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using System; +using System.Collections.Generic; +using System.Globalization; +namespace Org.IdentityConnectors.Common +{ + /// + /// Represents a Java Locale and has facilities for converting to/from + /// C# CultureInfo + /// + public sealed class Locale + { + private static readonly IDictionary + Locale2CultureInfoName = new Dictionary(); + private static readonly IDictionary + CultureInfoName2Locale = new Dictionary(); + + private static void AddMapping(Locale locale, String name) + { + Locale2CultureInfoName[locale] = name; + CultureInfoName2Locale[name] = locale; + } + + /// + /// Special cases + /// + static Locale() + { + AddMapping(new Locale(), ""); + AddMapping(new Locale("iw"), "he"); + AddMapping(new Locale("zh"), "zh-CHS"); + AddMapping(new Locale("iw", "IL"), "he-IL"); + AddMapping(new Locale("no", "NO"), "nb-NO"); + AddMapping(new Locale("no", "NO", "NY"), "nn-NO"); + } + + private String _language; + private String _country; + private String _variant; + + public Locale() + : this("") + { + + } + + public Locale(String language) + : this(language, "") + { + } + public Locale(String language, String country) + : this(language, country, "") + { + } + public Locale(String language, String country, String variant) + { + _language = language ?? ""; + _country = country ?? ""; + _variant = variant ?? ""; + } + + public string Language + { + get + { + return _language; + } + } + + public string Country + { + get + { + return _country; + } + } + + public string Variant + { + get + { + return _variant; + } + } + + public override bool Equals(Object o) + { + Locale other = o as Locale; + if (other != null) + { + if (!Object.Equals(Language, other.Language)) + { + return false; + } + if (!Object.Equals(Country, other.Country)) + { + return false; + } + if (!Object.Equals(Variant, other.Variant)) + { + return false; + } + return true; + } + return false; + } + + public override int GetHashCode() + { + unchecked + { + return _language.GetHashCode() + _country.GetHashCode(); + } + } + + public override string ToString() + { + return Language + "_" + Country + "_" + Variant; + } + + /// + /// Creates the closest CultureInfo that maps to this locale + /// + /// The culture info + public CultureInfo ToCultureInfo() + { + CultureInfo rv = null; + String code = CollectionUtil.GetValue(Locale2CultureInfoName, this, null); + if (code != null) + { + rv = TryCultureCode(code); + } + if (rv == null) + { + if (Country.Length > 0) + { + rv = TryCultureCode(Language + "-" + Country); + } + } + if (rv == null) + { + rv = TryCultureCode(Language); + } + if (rv == null) + { + rv = CultureInfo.InvariantCulture; + } + return rv; + } + + public static Locale FindLocale(CultureInfo info) + { + String code = info.Name; + Locale rv = CollectionUtil.GetValue(CultureInfoName2Locale, code, null); + if (rv == null) + { + String[] parts = code.Split(new string[] { "-" }, + StringSplitOptions.RemoveEmptyEntries); + String language = ""; + String country = ""; + if (parts.Length > 0) + { + String l = parts[0]; + if (LooksLikeValidLanguageCode(l)) + { + language = l; + if (parts.Length > 1) + { + String c = parts[1]; + if (LooksLikeValidCountryCode(c)) + { + country = c; + } + } + } + } + rv = new Locale(language, country); + } + return rv; + } + + /// + /// Weeds out some junk + /// + /// + /// + private static bool LooksLikeValidLanguageCode(String l) + { + char[] chars = l.ToCharArray(); + return (chars.Length == 2 && + Char.IsLower(chars[0]) && + Char.IsLower(chars[1])); + } + /// + /// Weeds out some junk + /// + /// + /// + private static bool LooksLikeValidCountryCode(String l) + { + char[] chars = l.ToCharArray(); + return (chars.Length == 2 && + Char.IsUpper(chars[0]) && + Char.IsUpper(chars[1])); + } + + private static CultureInfo TryCultureCode(String code) + { + try + { + return new CultureInfo(code); + } + catch (Exception) + { + return null; + } + } + } +} diff --git a/dotnet/framework/Common/Pair.cs b/dotnet/framework/Common/Pair.cs new file mode 100644 index 00000000..b26b35f4 --- /dev/null +++ b/dotnet/framework/Common/Pair.cs @@ -0,0 +1,103 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2014 ForgeRock AS. + */ +using System; + +namespace Org.IdentityConnectors.Common +{ + /// + /// Represents a Pair of objects. + /// + public class Pair + { + + public Pair() + { + } + + /// + /// + /// Obtains an immutable pair of from two objects inferring the generic + /// types. + /// + /// + /// + /// This factory allows the pair to be created using inference to obtain the + /// generic types. + /// + /// + /// @param + /// the left element type + /// @param + /// the right element type + /// + /// the left element, may be null + /// + /// the right element, may be null + /// a pair formed from the two parameters, not null + /// Since 1.4 + public static Pair Of(L left, R right) + { + return new Pair(left, right); + } + + public Pair(T1 first, T2 second) + { + First = first; + Second = second; + } + + public T1 First { get; set; } + public T2 Second { get; set; } + + public override bool Equals(object obj) + { + Pair other = obj as Pair; + if (other != null) + { + return Object.Equals(First, other.First) && + Object.Equals(Second, other.Second); + } + return false; + } + + public override int GetHashCode() + { + int rv = 0; + if (First != null) + { + rv ^= First.GetHashCode(); + } + if (Second != null) + { + rv ^= Second.GetHashCode(); + } + return rv; + } + + public override string ToString() + { + return "( " + First + ", " + Second + " )"; + } + } +} \ No newline at end of file diff --git a/dotnet/framework/Common/Pooling.cs b/dotnet/framework/Common/Pooling.cs new file mode 100644 index 00000000..6ef2db52 --- /dev/null +++ b/dotnet/framework/Common/Pooling.cs @@ -0,0 +1,251 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; + +using System.Collections.Generic; + +namespace Org.IdentityConnectors.Common.Pooling +{ + /// + /// Configuration for pooling objects + /// + public sealed class ObjectPoolConfiguration + { + + /// + /// Max objects (idle+active). + /// + private int _maxObjects = 10; + + /// + /// Max idle objects. + /// + private int _maxIdle = 10; + + /// + /// Max time to wait if the pool is waiting for a free object to become + /// available before failing. + /// + /// + /// Zero means don't wait + /// + private long _maxWait = 150 * 1000; + + /// + /// Minimum time to wait before evicting an idle object. + /// + /// + /// Zero means don't wait + /// + private long _minEvictableIdleTimeMillis = 120 * 1000; + + /// + /// Minimum number of idle objects. + /// + private int _minIdle = 1; + + + /// + /// Get the set number of maximum objects (idle+active) + /// + public int MaxObjects + { + get + { + return _maxObjects; + } + set + { + _maxObjects = value; + } + } + + /// + /// Get the maximum number of idle objects. + /// + public int MaxIdle + { + get + { + return _maxIdle; + } + set + { + _maxIdle = value; + } + } + + /// + /// Max time to wait if the pool is waiting for a free object to become + /// available before failing. + /// + /// + /// Zero means don't wait + /// + public long MaxWait + { + get + { + return _maxWait; + } + set + { + _maxWait = value; + } + } + + /// + /// Minimum time to wait before evicting an idle object. + /// + /// + /// Zero means don't wait + /// + public long MinEvictableIdleTimeMillis + { + get + { + return _minEvictableIdleTimeMillis; + } + set + { + _minEvictableIdleTimeMillis = value; + } + } + + /// + /// Minimum number of idle objects. + /// + public int MinIdle + { + get + { + return _minIdle; + } + set + { + _minIdle = value; + } + } + + // ======================================================================= + // Constructors + // ======================================================================= + + public ObjectPoolConfiguration() + { + } + + public ObjectPoolConfiguration(ObjectPoolConfiguration other) + { + this.MaxObjects = other.MaxObjects; + this.MaxIdle = other.MaxIdle; + this.MaxWait = other.MaxWait; + this.MinEvictableIdleTimeMillis = other.MinEvictableIdleTimeMillis; + this.MinIdle = other.MinIdle; + } + + public void Validate() + { + if (_minIdle < 0) + { + throw new ArgumentException("Min idle is less than zero."); + } + if (_maxObjects < 0) + { + throw new ArgumentException("Max active is less than zero."); + } + if (_maxIdle < 0) + { + throw new ArgumentException("Max idle is less than zero."); + } + if (_maxWait < 0) + { + throw new ArgumentException("Max wait is less than zero."); + } + if (_minEvictableIdleTimeMillis < 0) + { + throw new ArgumentException("Min evictable idle time millis less than zero."); + } + if (_minIdle > _maxIdle) + { + throw new ArgumentException("Min idle is greater than max idle."); + } + if (_maxIdle > _maxObjects) + { + throw new ArgumentException("Max idle is greater than max objects."); + } + } + + public override int GetHashCode() + { + unchecked + { + return (int)(MaxObjects + MaxIdle + MaxWait + MinEvictableIdleTimeMillis + MinIdle); + } + } + + public override bool Equals(Object obj) + { + if (obj is ObjectPoolConfiguration) + { + ObjectPoolConfiguration other = (ObjectPoolConfiguration)obj; + + if (MaxObjects != other.MaxObjects) + { + return false; + } + if (MaxIdle != other.MaxIdle) + { + return false; + } + if (MaxWait != other.MaxWait) + { + return false; + } + if (MinEvictableIdleTimeMillis != other.MinEvictableIdleTimeMillis) + { + return false; + } + if (MinIdle != other.MinIdle) + { + return false; + } + return true; + } + return false; + } + + public override String ToString() + { + // poor man's toString() + IDictionary bld = new Dictionary(); + bld["MaxObjects"] = MaxObjects; + bld["MaxIdle"] = MaxIdle; + bld["MaxWait"] = MaxWait; + bld["MinEvictableIdleTimeMillis"] = MinEvictableIdleTimeMillis; + bld["MinIdle"] = MinIdle; + return bld.ToString(); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/Common/Proxy.cs b/dotnet/framework/Common/Proxy.cs new file mode 100644 index 00000000..2b0dec83 --- /dev/null +++ b/dotnet/framework/Common/Proxy.cs @@ -0,0 +1,272 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using System; +using System.Reflection; +using System.Reflection.Emit; +using System.Collections.Generic; +namespace Org.IdentityConnectors.Common.Proxy +{ + /// + /// Similar to java.lang.reflect.InvocationHandler + /// + public interface InvocationHandler + { + Object + Invoke(Object proxy, MethodInfo method, Object[] args); + } + + + /// + /// Similar to java.lang.reflect.Proxy + /// + public static class Proxy + { + private static readonly MethodInfo INVOCATION_HANDLER_METHOD = + typeof(InvocationHandler).GetMethod("Invoke", + new Type[]{typeof(object), + typeof(MethodInfo), + typeof(object[])}); + private static readonly MethodInfo GET_METHOD_FROM_HANDLE_METHOD = + typeof(MethodBase).GetMethod("GetMethodFromHandle", + new Type[] { typeof(RuntimeMethodHandle) }); + + private static readonly object LOCK = new Object(); + + private static IDictionary + _implementationMap = new Dictionary(); + + private static int _count = 0; + + public static Object NewProxyInstance(Type interfaze, + InvocationHandler handler) + { + ConstructorInfo constructor = GetOrCreateConstructorInfo(interfaze); + return constructor.Invoke(new object[] { handler }); + } + + private static ConstructorInfo GetOrCreateConstructorInfo(Type type) + { + lock (LOCK) + { + ConstructorInfo rv = + CollectionUtil.GetValue(_implementationMap, type, null); + if (rv == null) + { + Type impl = GenerateImplementation(type); + rv = + impl.GetConstructor(new Type[] { typeof(InvocationHandler) }); + + _implementationMap[type] = rv; + } + return rv; + } + } + + private static String NextName() + { + int count; + lock (LOCK) + { + count = _count; + count++; + } + return "___proxy" + count; + } + + + + private static Type GenerateImplementation(Type interfaze) + { + if (!interfaze.IsInterface) + { + throw new ArgumentException("Type is not an interface: " + interfaze); + } + if (interfaze.IsGenericType) + { + throw new ArgumentException("Type is a generic type: " + interfaze); + } + + String uniqueName = NextName(); + + AssemblyName assemblyName = new AssemblyName(uniqueName); + AssemblyBuilder assemblyBuilder = + AppDomain.CurrentDomain.DefineDynamicAssembly( + assemblyName, + AssemblyBuilderAccess.RunAndSave); + // For a single-module assembly, the module name is usually + // the assembly name plus an extension. + ModuleBuilder moduleBuilder = + assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll"); + + TypeBuilder typeBuilder = moduleBuilder.DefineType( + uniqueName, + TypeAttributes.Public); + typeBuilder.AddInterfaceImplementation(interfaze); + + MethodInfo[] methods = interfaze.GetMethods(); + ConstructorBuilder classInitializer = + typeBuilder.DefineTypeInitializer(); + ILGenerator classInitializerCode = + classInitializer.GetILGenerator(); + //generate constructor and _handler field + FieldBuilder handlerField = + typeBuilder.DefineField("_handler", + typeof(InvocationHandler), + FieldAttributes.Private | FieldAttributes.InitOnly); + int count = 0; + foreach (MethodInfo method in methods) + { + GenerateMethod(typeBuilder, method, classInitializerCode, count, + handlerField); + count++; + } + classInitializerCode.Emit(OpCodes.Ret); + + + ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor( + MethodAttributes.Public, + CallingConventions.Standard, + new Type[] { typeof(InvocationHandler) }); + ILGenerator constructorCode = constructorBuilder.GetILGenerator(); + // For a constructor, argument zero is a reference to the new + // instance. Push it on the stack before calling the base + // class constructor. Specify the default constructor of the + // base class (System.Object) by passing an empty array of + // types (Type.EmptyTypes) to GetConstructor. + constructorCode.Emit(OpCodes.Ldarg_0); + constructorCode.Emit(OpCodes.Call, + typeof(object).GetConstructor(Type.EmptyTypes)); + constructorCode.Emit(OpCodes.Ldarg_0); + constructorCode.Emit(OpCodes.Ldarg_1); + constructorCode.Emit(OpCodes.Stfld, handlerField); + constructorCode.Emit(OpCodes.Ret); + + + Type t = typeBuilder.CreateType(); + + //assemblyBuilder.Save(assemblyName.Name+".dll"); + + return t; + } + + private static void ValidateMethod(MethodInfo info) + { + if (info.GetGenericArguments().Length != 0) + { + throw new ArgumentException( + "Method not supported since it has generic arguments: " + info); + + } + foreach (ParameterInfo parameter in info.GetParameters()) + { + if (parameter.IsOut) + { + throw new ArgumentException( + "Method not supported since it has output paramaters: " + info); + } + if (parameter.IsRetval) + { + throw new ArgumentException( + "Method not supported since it has retval paramaters: " + info); + } + } + } + + private static void GenerateMethod(TypeBuilder typeBuilder, + MethodInfo method, + ILGenerator classInitializerCode, + int methodCount, + FieldBuilder handlerField) + { + ValidateMethod(method); + + FieldBuilder methodField = + typeBuilder.DefineField("METHOD" + methodCount, + typeof(MethodInfo), + FieldAttributes.Private | FieldAttributes.InitOnly | FieldAttributes.Static); + + + + classInitializerCode.Emit(OpCodes.Ldtoken, + method); + classInitializerCode.Emit(OpCodes.Call, + GET_METHOD_FROM_HANDLE_METHOD); + classInitializerCode.Emit(OpCodes.Castclass, typeof(MethodInfo)); + classInitializerCode.Emit(OpCodes.Stsfld, methodField); + + + Type[] parameterTypes = ReflectionUtil.GetParameterTypes(method); + MethodBuilder methodBuilder = typeBuilder.DefineMethod( + method.Name, + MethodAttributes.Public | + MethodAttributes.HideBySig | + MethodAttributes.NewSlot | + MethodAttributes.Virtual | + MethodAttributes.Final, + method.CallingConvention, + method.ReturnType, + parameterTypes); + + ILGenerator methodCode = methodBuilder.GetILGenerator(); + methodCode.Emit(OpCodes.Ldarg_0); + methodCode.Emit(OpCodes.Ldfld, handlerField); + methodCode.Emit(OpCodes.Ldarg_0); + methodCode.Emit(OpCodes.Ldsfld, methodField); + methodCode.Emit(OpCodes.Ldc_I4, parameterTypes.Length); + methodCode.Emit(OpCodes.Newarr, typeof(object)); + + for (int index = 0; index < parameterTypes.Length; index++) + { + Type parameterType = parameterTypes[index]; + methodCode.Emit(OpCodes.Dup); + methodCode.Emit(OpCodes.Ldc_I4, index); + methodCode.Emit(OpCodes.Ldarg, index + 1); + if (parameterType.IsValueType) + { + methodCode.Emit(OpCodes.Box, parameterType); + } + methodCode.Emit(OpCodes.Stelem_Ref); + } + + methodCode.Emit(OpCodes.Callvirt, INVOCATION_HANDLER_METHOD); + Type returnType = method.ReturnType; + + if (typeof(void).Equals(returnType)) + { + methodCode.Emit(OpCodes.Pop); + } + else if (returnType.IsValueType) + { + methodCode.Emit(OpCodes.Unbox_Any, returnType); + } + else + { + methodCode.Emit(OpCodes.Castclass, returnType); + } + + methodCode.Emit(OpCodes.Ret); + + typeBuilder.DefineMethodOverride(methodBuilder, method); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/Common/ReflectionUtil.cs b/dotnet/framework/Common/ReflectionUtil.cs new file mode 100644 index 00000000..6b7fbe7c --- /dev/null +++ b/dotnet/framework/Common/ReflectionUtil.cs @@ -0,0 +1,171 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Linq; +namespace Org.IdentityConnectors.Common +{ + /// + /// Miscellaenous reflection utilities. + /// + public static class ReflectionUtil + { + /// + /// Gets all the interfaces in the hierarchy + /// + /// + /// + public static Type[] GetAllInterfaces(Type type) + { + HashSet temp = new HashSet(); + GetAllInterfaces2(type, temp); + return temp.ToArray(); + } + + private static void GetAllInterfaces2(Type type, HashSet rv) + { + if (type != null) + { + if (type.IsInterface) + { + rv.Add(type); + } + GetAllInterfaces2(type.BaseType, rv); + foreach (Type inter in type.GetInterfaces()) + { + GetAllInterfaces2(inter, rv); + } + } + } + + /// + /// Returns the generic type definition of the given type + /// + /// + /// + public static Type[] GetTypeErasure(Type[] types) + { + Type[] rv = new Type[types.Length]; + for (int i = 0; i < types.Length; i++) + { + rv[i] = GetTypeErasure(types[i]); + } + return rv; + } + /// + /// Returns the generic type definition of the given type + /// + /// + /// + public static Type GetTypeErasure(Type type) + { + if (type.IsGenericType) + { + type = type.GetGenericTypeDefinition(); + } + return type; + } + + /// + /// Returns true iff t1 is a parent type of t2. Unlike + /// Type.isAssignableFrom, this handles generic types as + /// well. + /// + /// + /// + /// + public static bool IsParentTypeOf(Type t1, + Type t2) + { + if (t2 == null) + { + return t1 == null; + } + Type found = FindInHierarchyOf(t1, t2); + return found != null; + } + + /// + /// Finds t1 in the hierarchy of t2 or null if not found. The + /// returned value will have generic parameters applied to it. + /// + /// + /// + /// + public static Type FindInHierarchyOf(Type t1, Type t2) + { + if (t2 == null) + { + return null; + } + if (EqualsIgnoreGeneric(t1, t2)) + { + return t2; + } + Type found1 = FindInHierarchyOf(t1, t2.BaseType); + if (found1 != null) + { + return found1; + } + foreach (Type inter in t2.GetInterfaces()) + { + Type found2 = FindInHierarchyOf(t1, inter); + if (found2 != null) + { + return found2; + } + } + return null; + } + + private static bool EqualsIgnoreGeneric(Type t1, + Type t2) + { + if (t1 == null || t2 == null) + { + return t1 == null && t2 == null; + } + if (t1.IsGenericType) + { + t1 = t1.GetGenericTypeDefinition(); + } + if (t2.IsGenericType) + { + t2 = t2.GetGenericTypeDefinition(); + } + return t1.Equals(t2); + } + + public static Type[] GetParameterTypes(MethodInfo method) + { + ParameterInfo[] parameters = method.GetParameters(); + Type[] rv = new Type[parameters.Length]; + for (int i = 0; i < parameters.Length; i++) + { + rv[i] = parameters[i].ParameterType; + } + return rv; + } + } +} \ No newline at end of file diff --git a/dotnet/framework/Common/SafeType.cs b/dotnet/framework/Common/SafeType.cs new file mode 100644 index 00000000..ef953bfd --- /dev/null +++ b/dotnet/framework/Common/SafeType.cs @@ -0,0 +1,143 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; + +namespace Org.IdentityConnectors.Common +{ + /// + /// The equivalent of java's Class<? extends...%gt; syntax. + /// Allows you to restrict a Type to a certain class hierarchy. + /// + public sealed class SafeType + where T : class + { + private readonly Type _rawType; + + /// + /// Make private so no one can create directly + /// + private SafeType(Type rawType) + { + if (!ReflectionUtil.IsParentTypeOf(typeof(T), rawType)) + { + throw new ArgumentException("Type: " + rawType + " is not a subclass of" + typeof(T)); + } + _rawType = rawType; + } + + /// + /// Returns the SafeType for a given raw type. + /// NOTE: If possible use Get() instead since it is statically + /// checked at compile time. + /// + /// + /// + public static SafeType ForRawType(Type type) + { + return new SafeType(type); + } + + /// + /// Gets an instance of the safe type in a type-safe fashion. + /// + /// The instance of the safe type + public static SafeType Get() + where U : T + { + return new SafeType(typeof(U)); + } + + /// + /// Gets an instance of the safe type in a type-safe fashion from an object. + /// + /// The instance of the safe type + public static SafeType Get(T obj) + { + return new SafeType(obj.GetType()); + } + + /// + /// Returns the generic type definition corresponding to this type. + /// Will return the same type if this is already a generic type. + /// + /// + public SafeType GetTypeErasure() + { + return SafeType.ForRawType(ReflectionUtil.GetTypeErasure(RawType)); + } + + /// + /// Returns the underlying C# type + /// + public Type RawType + { + get + { + return _rawType; + } + } + + /// + /// Creates a new instance of the given type + /// + /// The new instance + public T CreateInstance() + { + return (T)Activator.CreateInstance(RawType); + } + + /// + /// Returns true iff these represent the same underlying type + /// and the SafeType has the same parent type. + /// + /// The other + /// true iff these represent the same underylying type + /// and the TypeGroup has the same parent type + public override bool Equals(object o) + { + if (o is SafeType) + { + SafeType other = (SafeType)o; + return RawType.Equals(other.RawType); + } + return false; + } + /// + /// Returns a hash of the type + /// + /// a hash of the type + public override int GetHashCode() + { + return RawType.GetHashCode(); + } + /// + /// Returns a string representation of the member type + /// + /// a string representation of the member type + public override string ToString() + { + return RawType.ToString(); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/Common/Script.cs b/dotnet/framework/Common/Script.cs new file mode 100644 index 00000000..023ef1b3 --- /dev/null +++ b/dotnet/framework/Common/Script.cs @@ -0,0 +1,182 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.IO; +using System.Reflection; +using System.Collections.Generic; + +namespace Org.IdentityConnectors.Common.Script +{ + public interface ScriptExecutor + { + /// + /// Executes the script with the given arguments. + /// + /// key/value set of variables to + /// pass to the script. + /// + object Execute(IDictionary arguments); + } + public abstract class ScriptExecutorFactory + { + + private static readonly object LOCK = new object(); + + /// + /// Loaded w/ all supported languages. + /// + private static IDictionary _supportedLanguages = null; + + /// + /// Load all script executor factory assemblies in the same directory + /// the 'Common' assembly. + /// + private static IDictionary LoadSupportedLanguages() + { + // attempt to process all assemblies.. + IDictionary ret = new Dictionary(); + Assembly assembly = Assembly.GetExecutingAssembly(); + FileInfo thisAssemblyFile = new FileInfo(assembly.Location); + DirectoryInfo directory = thisAssemblyFile.Directory; + // get all *ScriptExecutorFactory assmebly from the current directory + FileInfo[] files = directory.GetFiles("*.ScriptExecutorFactory.dll"); + Type t = typeof(ScriptExecutorFactoryClassAttribute); + foreach (FileInfo file in files) + { + try + { + Assembly lib = Assembly.LoadFrom(file.ToString()); + foreach (Type type in lib.GetTypes()) + { + Object[] attributes = type.GetCustomAttributes(t, false); + if (attributes.Length > 0) + { + ScriptExecutorFactoryClassAttribute attribute = + (ScriptExecutorFactoryClassAttribute)attributes[0]; + // attempt to test assembly.. + Activator.CreateInstance(type); + // if we made it this far its okay + ret[attribute.Language.ToUpper()] = type; + } + } + } + catch (Exception e) + { + TraceUtil.TraceException("Unable to load assembly: " + + assembly.FullName + ". This is a fatal exception: ", e); + throw; + } + } + return ret; + } + + private static IDictionary GetSupportedLanguages() + { + lock (LOCK) + { + if (_supportedLanguages == null) + { + _supportedLanguages = LoadSupportedLanguages(); + } + return _supportedLanguages; + } + } + + /// + /// Returns the set of supported languages. + /// + /// The set of supported languages. + public static ICollection SupportedLanguages + { + get + { + IDictionary map = + GetSupportedLanguages(); + return CollectionUtil.AsReadOnlySet(map.Keys); + } + } + + /// + /// Creates a ScriptExecutorFactory for the given language + /// + /// The name of the language + /// The script executor factory + /// If the given language is not + /// supported. + public static ScriptExecutorFactory NewInstance(String language) + { + if (language == null) + { + throw new ArgumentException("Language must be specified"); + } + Type type = CollectionUtil.GetValue(GetSupportedLanguages(), language.ToUpper(), null); + if (type == null) + { + throw new ArgumentException("Language not supported: " + language); + } + return (ScriptExecutorFactory)Activator.CreateInstance(type); + } + + + /// + /// Creates a script executor for the given script. + /// + /// The classloader that contains the java classes + /// that the script should have access to. + /// The script text. + /// A hint to tell the script executor whether or + /// not to compile the given script. This need not be implemented + /// by all script executors. If true, the caller is saying that + /// they intend to call the script multiple times with different + /// arguments, so compile if possible. + /// A script executor. + public abstract ScriptExecutor NewScriptExecutor( + Assembly[] referencedAssemblies, + String script, + bool compile); + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ScriptExecutorFactoryClassAttribute : System.Attribute + { + private readonly string _lang; + + /// + /// Determine the language supported by the factory. + /// + /// + public ScriptExecutorFactoryClassAttribute(string lang) + { + _lang = lang; + } + + public string Language + { + get + { + return _lang; + } + } + } +} \ No newline at end of file diff --git a/dotnet/framework/Common/Security.cs b/dotnet/framework/Common/Security.cs new file mode 100644 index 00000000..23dac12b --- /dev/null +++ b/dotnet/framework/Common/Security.cs @@ -0,0 +1,1008 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014-2015 ForgeRock AS. + */ +using System; +using System.Linq; +using System.Security; +using System.Security.Cryptography; +using System.Runtime.InteropServices; +using System.Text; +namespace Org.IdentityConnectors.Common.Security +{ + #region UnmanagedArray + /// + /// Places an array facade on an unmanaged memory data structure that + /// holds senstive data. (In C# placing senstive data in managed + /// memory is considered unsecure since the memory model allows + /// data to be copied around). + /// + public interface UnmanagedArray : IDisposable + { + int Length { get; } + T this[int index] { get; set; } + } + #endregion + + #region GuardedByteArray + /// + /// Secure byte array implementation that solves the problems associated with + /// keeping confidential data as byte[]. + /// + /// + /// That is, anything + /// represented as a byte[] is kept in memory in clear + /// text and stays in memory at least until it is garbage collected. + /// + /// The GuardedByteArray class alleviates this problem by storing the bytes in + /// memory in an encrypted form. The encryption key will be a randomly-generated + /// key. + /// + /// + /// In their serialized form, GuardedByteArray will be encrypted using a known + /// default key. This is to provide a minimum level of protection regardless + /// of the transport. For communications with the Remote Connector Framework + /// it is recommended that deployments enable SSL for true encryption. + /// + /// + /// Applications may also wish to persist GuardedByteArrays. In the case of + /// Identity Manager, it should convert GuardedByteArrays to EncryptedData so + /// that they can be stored and managed using the Manage Encryption features + /// of Identity Manager. Other applications may wish to serialize APIConfiguration + /// as a whole. These applications are responsible for encrypting the APIConfiguration + /// blob for an additional layer of security (beyond the basic default key encryption + /// provided by GuardedByteArray). + /// + /// + public sealed class GuardedByteArray : IDisposable + { + + /// + /// Callback interface for those times that it is necessary to access the + /// clear text of the guarded bytes. + /// + public interface Accessor + { + + /// + /// This method will be called with the clear text of the byte array. + /// + /// + /// After the call the clearBytes array will be automatically zeroed + /// out, thus keeping the window of potential exposure to a bare-minimum. + /// + /// + void Access(UnmanagedArray clearBytes); + } + + public class LambdaAccessor : Accessor + { + private readonly Action> _accessor; + + public LambdaAccessor(Action> accessor) + { + _accessor = accessor; + } + + public void Access(UnmanagedArray clearBytes) + { + _accessor(clearBytes); + } + } + + private SecureString _target; + private String _base64SHA1Hash; + + /// + /// Creates an empty secure byte array. + /// + public GuardedByteArray() + { + _target = new SecureString(); + ComputeHash(); + } + + public GuardedByteArray(UnmanagedArray clearBytes) + { + _target = new SecureString(); + AppendBytes(clearBytes); + } + + private GuardedByteArray(SecureString str) + { + _target = str.Copy(); + ComputeHash(); + } + + + /// + /// Provides access to the clear-text value of the bytes in a controlled fashion. + /// + /// + /// The clear-text bytes will only be available for the duration of the call + /// and automatically zeroed out following the call. + /// + /// NOTE: Callers are encouraged to use + /// where possible if the intended use is merely to verify the contents of + /// the string match an expected hash value. + /// + /// + /// Accessor callback. + /// If the byte array has been disposed + public void Access(Accessor accessor) + { + using (SecureStringToByteArrayAdapter adapter = new SecureStringToByteArrayAdapter(_target)) + { + accessor.Access(adapter); + } + } + + /// + /// Appends a single clear-text byte to the secure byte array. + /// + /// + /// The in-memory data will be decrypted, the character will be + /// appended, and then it will be re-encrypted. + /// + /// The byte to append. + /// If the byte array is read-only + /// If the byte array has been disposed + public void AppendByte(byte b) + { + _target.AppendChar((char)b); + ComputeHash(); + } + + private void AppendBytes(UnmanagedArray clearBytes) + { + for (int i = 0; i < clearBytes.Length; i++) + { + _target.AppendChar((char)clearBytes[i]); + } + ComputeHash(); + } + + /// + /// Clears the in-memory representation of the byte array. + /// + public void Dispose() + { + _target.Dispose(); + } + + /// + /// Returns true iff this byte array has been marked read-only + /// + /// true iff this byte array has been marked read-only + /// If the byte array has been disposed + public bool IsReadOnly() + { + return _target.IsReadOnly(); + } + + /// + /// Mark this byte array as read-only. + /// + /// If the byte array has been disposed + public void MakeReadOnly() + { + _target.MakeReadOnly(); + } + + /// + /// Create a copy of the byte array. + /// + /// + /// If this instance is read-only, + /// the copy will not be read-only. + /// + /// A copy of the byte array. + /// If the byte array has been disposed + public GuardedByteArray Copy() + { + SecureString t2 = _target.Copy(); + GuardedByteArray rv = new GuardedByteArray(t2); + return rv; + } + + /// + /// Verifies that this base-64 encoded SHA1 hash of this byte array + /// matches the given value. + /// + /// The hash to verify against. + /// True if the hash matches the given parameter. + /// If the byte array has been disposed + public bool VerifyBase64SHA1Hash(String hash) + { + CheckNotDisposed(); + return _base64SHA1Hash.Equals(hash); + } + + public string GetBase64SHA1Hash() + { + CheckNotDisposed(); + return _base64SHA1Hash; + } + + + + private void CheckNotDisposed() + { + //this throws if disposed + _target.IsReadOnly(); + } + + + public override bool Equals(Object o) + { + if (o is GuardedByteArray) + { + GuardedByteArray other = (GuardedByteArray)o; + //not the true contract of equals. however, + //due to the high mathematical improbability of + //two unequal strings having the same secure hash, + //this approach feels good. the alternative, + //decrypting for comparison, is simply too + //performance intensive to be used for equals + return _base64SHA1Hash.Equals(other._base64SHA1Hash); + } + return false; + } + + public override int GetHashCode() + { + return _base64SHA1Hash.GetHashCode(); + } + + private void ComputeHash() + { + Access(new LambdaAccessor(array => + { + _base64SHA1Hash = SecurityUtil.ComputeBase64SHA1Hash(array); + })); + } + + } + #endregion + + #region GuardedString + /// + /// Secure string implementation that solves the problems associated with + /// keeping passwords as java.lang.String. + /// + /// + /// That is, anything + /// represented as a String is kept in memory as a clear + /// text password and stays in memory at least until it is garbage collected. + /// + /// The GuardedString class alleviates this problem by storing the characters in + /// memory in an encrypted form. The encryption key will be a randomly-generated + /// key. + /// + /// + /// In their serialized form, GuardedString will be encrypted using a known + /// default key. This is to provide a minimum level of protection regardless + /// of the transport. For communications with the Remote Connector Framework + /// it is recommended that deployments enable SSL for true encryption. + /// + /// + /// Applications may also wish to persist GuardedStrings. In the case of + /// Identity Manager, it should convert GuardedStrings to EncryptedData so + /// that they can be stored and managed using the Manage Encryption features + /// of Identity Manager. Other applications may wish to serialize APIConfiguration + /// as a whole. These applications are responsible for encrypting the APIConfiguration + /// blob for an additional layer of security (beyond the basic default key encryption + /// provided by GuardedString). + /// + /// + public sealed class GuardedString : IDisposable + { + + /// + /// Callback interface for those times that it is necessary to access the + /// clear text of the secure string. + /// + public interface Accessor + { + + /// + /// This method will be called with the clear text of the string. + /// + /// + /// After the call the clearChars array will be automatically zeroed + /// out, thus keeping the window of potential exposure to a bare-minimum. + /// + /// + void Access(UnmanagedArray clearChars); + } + + public class LambdaAccessor : Accessor + { + private readonly Action> _accessor; + + public LambdaAccessor(Action> accessor) + { + _accessor = accessor; + } + + public void Access(UnmanagedArray clearBytes) + { + _accessor(clearBytes); + } + } + + private SecureString _target; + private String _base64SHA1Hash; + + /// + /// Creates an empty secure string + /// + public GuardedString() + { + _target = new SecureString(); + ComputeHash(); + } + + public GuardedString(SecureString str) + { + _target = str.Copy(); + ComputeHash(); + } + + + /// + /// Provides access to the clear-text value of the string in a controlled fashion. + /// + /// + /// The clear-text characters will only be available for the duration of the call + /// and automatically zeroed out following the call. + /// + /// NOTE: Callers are encouraged to use + /// where possible if the intended use is merely to verify the contents of + /// the string match an expected hash value. + /// + /// + /// Accessor callback. + /// If the string has been disposed + public void Access(Accessor accessor) + { + using (SecureStringAdapter adapter = new SecureStringAdapter(_target)) + { + accessor.Access(adapter); + } + } + + /// + /// Appends a single clear-text character to the secure string. + /// + /// + /// The in-memory data will be decrypted, the character will be + /// appended, and then it will be re-encrypted. + /// + /// The character to append. + /// If the string is read-only + /// If the string has been disposed + public void AppendChar(char c) + { + _target.AppendChar(c); + ComputeHash(); + } + + /// + /// Clears the in-memory representation of the string. + /// + public void Dispose() + { + _target.Dispose(); + } + + /// + /// Returns true iff this string has been marked read-only + /// + /// true iff this string has been marked read-only + /// If the string has been disposed + public bool IsReadOnly() + { + return _target.IsReadOnly(); + } + + /// + /// Mark this string as read-only. + /// + /// If the string has been disposed + public void MakeReadOnly() + { + _target.MakeReadOnly(); + } + + /// + /// Create a copy of the string. + /// + /// + /// If this instance is read-only, + /// the copy will not be read-only. + /// + /// A copy of the string. + /// If the string has been disposed + public GuardedString Copy() + { + SecureString t2 = _target.Copy(); + GuardedString rv = new GuardedString(t2); + return rv; + } + + /// + /// Verifies that this base-64 encoded SHA1 hash of this string + /// matches the given value. + /// + /// The hash to verify against. + /// True if the hash matches the given parameter. + /// If the string has been disposed + public bool VerifyBase64SHA1Hash(String hash) + { + CheckNotDisposed(); + return _base64SHA1Hash.Equals(hash); + } + + public string GetBase64SHA1Hash() + { + CheckNotDisposed(); + return _base64SHA1Hash; + } + + + + private void CheckNotDisposed() + { + //this throws if disposed + _target.IsReadOnly(); + } + + + public override bool Equals(Object o) + { + if (o is GuardedString) + { + GuardedString other = (GuardedString)o; + //not the true contract of equals. however, + //due to the high mathematical improbability of + //two unequal strings having the same secure hash, + //this approach feels good. the alternative, + //decrypting for comparison, is simply too + //performance intensive to be used for equals + return _base64SHA1Hash.Equals(other._base64SHA1Hash); + } + return false; + } + + public override int GetHashCode() + { + return _base64SHA1Hash.GetHashCode(); + } + + public SecureString ToSecureString() + { + return _target.Copy(); + } + + private void ComputeHash() + { + Access(new LambdaAccessor(array => + { + _base64SHA1Hash = SecurityUtil.ComputeBase64SHA1Hash(array); + })); + } + + } + #endregion + + #region AbstractUnmanagedArray + public abstract class AbstractUnmanagedArray : UnmanagedArray + { + private readonly int _length; + private bool _disposed; + public AbstractUnmanagedArray(int length) + { + if (length < 0) + { + throw new ArgumentException("Invalid length: " + length); + } + _length = length; + } + public int Length + { + get + { + if (_disposed) + { + throw new ObjectDisposedException("UnmanagedArray"); + } + return _length; + } + } + public T this[int index] + { + get + { + if (_disposed) + { + throw new ObjectDisposedException("UnmanagedArray"); + } + if (index < 0 || index >= Length) + { + throw new IndexOutOfRangeException(); + } + return GetValue(index); + } + set + { + if (_disposed) + { + throw new ObjectDisposedException("SecureStringAdapter"); + } + if (index < 0 || index >= Length) + { + throw new IndexOutOfRangeException(); + } + SetValue(index, value); + } + } + public void Dispose() + { + if (!_disposed) + { + for (int i = 0; i < Length; i++) + { + this[i] = default(T); + } + _disposed = true; + FreeMemory(); + } + } + + abstract protected T GetValue(int index); + abstract protected void SetValue(int index, T val); + abstract protected void FreeMemory(); + } + #endregion + + #region SecureStringAdapter + internal class SecureStringAdapter : AbstractUnmanagedArray + { + private IntPtr _bstrPtr; + public SecureStringAdapter(SecureString secureString) + : base(secureString.Length) + { + Assertions.NullCheck(secureString, "secureString"); + _bstrPtr = Marshal.SecureStringToBSTR(secureString); + } + protected override char GetValue(int index) + { + unsafe + { + char* charPtr = (char*)_bstrPtr; + return *(charPtr + index); + } + } + protected override void SetValue(int index, char c) + { + unsafe + { + char* charPtr = (char*)_bstrPtr; + *(charPtr + index) = c; + } + } + protected override void FreeMemory() + { + Marshal.ZeroFreeBSTR(_bstrPtr); + } + } + #endregion + + #region SecureStringToByteArrayAdapter + internal class SecureStringToByteArrayAdapter : AbstractUnmanagedArray + { + private IntPtr _bstrPtr; + public SecureStringToByteArrayAdapter(SecureString secureString) + : base(secureString.Length) + { + Assertions.NullCheck(secureString, "secureString"); + _bstrPtr = Marshal.SecureStringToBSTR(secureString); + } + protected override byte GetValue(int index) + { + unsafe + { + char* charPtr = (char*)_bstrPtr; + return (byte)*(charPtr + index); + } + } + protected override void SetValue(int index, byte b) + { + unsafe + { + char* charPtr = (char*)_bstrPtr; + *(charPtr + index) = (char)b; + } + } + protected override void FreeMemory() + { + Marshal.ZeroFreeBSTR(_bstrPtr); + } + } + #endregion + + #region UnmanagedCharArray + public class UnmanagedCharArray : AbstractUnmanagedArray + { + private IntPtr _ptr; + public UnmanagedCharArray(int length) + : base(length) + { + unsafe + { + _ptr = Marshal.AllocHGlobal(length * sizeof(char)); + } + } + protected override char GetValue(int index) + { + unsafe + { + char* charPtr = (char*)_ptr; + return *(charPtr + index); + } + } + protected override void SetValue(int index, char c) + { + unsafe + { + char* charPtr = (char*)_ptr; + *(charPtr + index) = c; + } + } + protected override void FreeMemory() + { + Marshal.FreeHGlobal(_ptr); + } + } + #endregion + + #region UnmanagedByteArray + public class UnmanagedByteArray : AbstractUnmanagedArray + { + private IntPtr _ptr; + public UnmanagedByteArray(int length) + : base(length) + { + unsafe + { + _ptr = Marshal.AllocHGlobal(length * sizeof(byte)); + } + } + protected override byte GetValue(int index) + { + unsafe + { + byte* charPtr = (byte*)_ptr; + return *(charPtr + index); + } + } + protected override void SetValue(int index, byte c) + { + unsafe + { + byte* charPtr = (byte*)_ptr; + *(charPtr + index) = c; + } + } + protected override void FreeMemory() + { + Marshal.FreeHGlobal(_ptr); + } + } + #endregion + + #region SecurityUtil + /// + /// Description of SecurityUtil. + /// + public static class SecurityUtil + { + public static string[] LowerHexArray = Enumerable.Range(0, 256).Select(v => v.ToString("x2")).ToArray(); + public static string[] UpperHexArray = Enumerable.Range(0, 256).Select(v => v.ToString("X2")).ToArray(); + + /// + /// Converts chars to bytes without using any external functions + /// that might allocate additional buffers for the potentially + /// sensitive data. + /// + /// + /// This guarantees the caller that they only + /// need to cleanup the input and result. + /// + /// The chars + /// The bytes + public static UnmanagedArray CharsToBytes(UnmanagedArray chars) + { + UnmanagedByteArray bytes = new UnmanagedByteArray(chars.Length * 2); + + for (int i = 0; i < chars.Length; i++) + { + char v = chars[i]; + bytes[i * 2] = (byte)(0xff & (v >> 8)); + bytes[i * 2 + 1] = (byte)(0xff & (v)); + } + return bytes; + } + + /// + /// Converts bytes to chars without using any external functions + /// that might allocate additional buffers for the potentially + /// sensitive data. + /// + /// + /// This guarantees the caller that they only + /// need to cleanup the input and result. + /// + /// The bytes + /// The chars + public static UnmanagedArray BytesToChars(UnmanagedArray bytes) + { + UnmanagedCharArray chars = new UnmanagedCharArray(bytes.Length / 2); + for (int i = 0; i < chars.Length; i++) + { + char v = (char)((bytes[i * 2] << 8) | bytes[i * 2 + 1]); + chars[i] = v; + } + return chars; + } + + + public unsafe static string ComputeBase64SHA1Hash(UnmanagedArray input) + { + using (UnmanagedArray bytes = SecurityUtil.CharsToBytes(input)) + { + return ComputeBase64SHA1Hash(bytes); + } + } + + public unsafe static string ComputeBase64SHA1Hash(UnmanagedArray input) + { + byte[] managedBytes = new byte[input.Length]; + fixed (byte* dummy = managedBytes) + { //pin it + try + { + //populate it in pinned block + SecurityUtil.UnmanagedBytesToManagedBytes(input, managedBytes); + SHA1 hasher = SHA1.Create(); + byte[] data = hasher.ComputeHash(managedBytes); + return Convert.ToBase64String(data); + } + finally + { + //clear it before we leave pinned block + SecurityUtil.Clear(managedBytes); + } + } + } + + /// + /// Computes the Hex encoded SHA1 hash of the input. + /// + /// + /// The input bytes. + /// + /// {@code true} converts to lowercase or {@code false} to + /// uppercase + /// the hash (computed from the input bytes). + /// since 1.5 + public static string ComputeHexSHA1Hash(byte[] bytes, bool toLowerCase) + { + SHA1 hasher = SHA1.Create(); + byte[] data = hasher.ComputeHash(bytes); + return BytesToHex(data, toLowerCase); + } + + /// + /// Computes the Hex encoded input. + /// + /// + /// The input bytes to convert to Hex characters + /// + /// {@code true} converts to lowercase or {@code false} to + /// uppercase + /// A String containing hexadecimal characters + /// since 1.5 + public static string BytesToHex(byte[] bytes, bool toLowerCase) + { + StringBuilder hexChars = new StringBuilder(bytes.Length * 2); + string[] hexArray = toLowerCase ? LowerHexArray : UpperHexArray; + foreach (var v in bytes) + hexChars.Append(hexArray[v]); + return hexChars.ToString(); + } + + /// + /// Copies an unmanaged byte array into a managed byte array. + /// + /// + /// NOTE: it is imperative for security reasons that this only + /// be done in a context where the byte array in question is pinned. + /// moreover, the byte array must be cleared prior to leaving the + /// pinned block + /// + public static void UnmanagedBytesToManagedBytes(UnmanagedArray array, + byte[] bytes) + { + for (int i = 0; i < array.Length; i++) + { + bytes[i] = array[i]; + } + } + + /// + /// Clears an array of potentially sensitive bytes + /// + /// The bytes. May be null. + /// NOTE: because this is C#, this alone is not enough. The + /// array must be pinned during the interval it is in-use or + /// it could be copied out from under you. + public static void Clear(byte[] bytes) + { + if (bytes != null) + { + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = 0; + } + } + } + + /// + /// Clears an array of potentially sensitive chars + /// + /// The characters. May be null. + /// NOTE: because this is C#, this alone is not enough. The + /// array must be pinned during the interval it is in-use or + /// it could be copied out from under you. + public static void Clear(char[] chars) + { + if (chars != null) + { + for (int i = 0; i < chars.Length; i++) + { + chars[i] = (char)0; + } + } + } + + public static bool VerifyBase64SHA1Hash(UnmanagedArray input, string hash) + { + string inputHash = ComputeBase64SHA1Hash(input); + return inputHash.Equals(hash); + } + + /// + /// Decrypts the value of a . + /// + /// + /// the guarded string value. + /// the clear string value. + /// Since 1.4 + public static string Decrypt(GuardedString guardedString) + { + StringBuilder buf = new StringBuilder(); + guardedString.Access(new GuardedString.LambdaAccessor( + array => + { + for (int i = 0; i < array.Length; i++) + { + buf.Append(array[i]); + } + })); + return buf.ToString(); + } + + /// + /// Decrypts the value of a . + /// + /// + /// the guarded byte array value. + /// the clear byte array value. + /// Since 1.4 + public static byte[] Decrypt(GuardedByteArray guardedByteArray) + { + byte[] result = null; + guardedByteArray.Access(new GuardedByteArray.LambdaAccessor( + array => + { + result = new byte[array.Length]; + for (int i = 0; i < array.Length; i++) + { + result[i] = array[i]; + } + })); + return result; + } + } + #endregion + + #region Encryptor + /// + /// Responsible for encrypting/decrypting bytes. + /// + /// + /// Implementations + /// are intended to be thread-safe. + /// + public interface Encryptor + { + /// + /// Decrypts the given byte array + /// + /// The encrypted bytes + /// The decrypted bytes + UnmanagedArray Decrypt(byte[] bytes); + + /// + /// Encrypts the given byte array + /// + /// The clear bytes + /// The ecnrypted bytes + byte[] Encrypt(UnmanagedArray bytes); + } + #endregion + + #region EncryptorFactory + public abstract class EncryptorFactory + { + private static readonly object LOCK = new object(); + + // At some point we might make this pluggable, but for now, hard-code + private const String IMPL_NAME = "Org.IdentityConnectors.Common.Security.Impl.EncryptorFactoryImpl"; + + private static EncryptorFactory _instance; + + /// + /// Get the singleton instance of the . + /// + public static EncryptorFactory GetInstance() + { + lock (LOCK) + { + if (_instance == null) + { + Type type = FrameworkInternalBridge.LoadType(IMPL_NAME); + _instance = (EncryptorFactory)Activator.CreateInstance(type); + } + return _instance; + } + } + + /// + /// Default encryptor that encrypts/descrypts using a default key + /// + public abstract Encryptor GetDefaultEncryptor(); + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/Common/StringUtil.cs b/dotnet/framework/Common/StringUtil.cs new file mode 100644 index 00000000..008cb735 --- /dev/null +++ b/dotnet/framework/Common/StringUtil.cs @@ -0,0 +1,645 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Specialized; +using System.Text; +using System.Text.RegularExpressions; +using System.Diagnostics; +using System.Collections.Generic; +using System.Linq; +using Org.IdentityConnectors.Common.Security; + +namespace Org.IdentityConnectors.Common +{ + public static class StringUtil + { + + /// + /// Finds the index of the first digit and starts from the index specified. + /// + /// + /// String to search for a digit. + /// + /// Starting index from which to search + /// -1 if not found otherwise the index. + public static int IndexOfDigit(string str, int startidx) + { + int ret = -1; + if (str != null) + { + for (int i = startidx; i < str.Length; i++) + { + // get the first digit.. + if (char.IsDigit(str[i])) + { + ret = i; + break; + } + } + } + return ret; + } + + /// + /// Finds the index of the first digit. + /// + /// + /// String to seach for a digit. + /// -1 if not found otherwise the index. + public static int IndexOfDigit(string str) + { + return IndexOfDigit(str, 0); + } + + /// + /// Finds the index of the first non digit and starts from the index + /// specified. + /// + /// + /// String to seach for a non digit. + /// + /// Starting index from which to search. + /// -1 if not found otherwise the index. + public static int IndexOfNonDigit(string str, int startidx) + { + int ret = -1; + if (str != null) + { + for (int i = startidx; i < str.Length; i++) + { + // get the first digit.. + if (!char.IsDigit(str[i])) + { + ret = i; + break; + } + } + } + return ret; + } + + /// + /// Finds the index of the first non digit. + /// + /// + /// String to seach for a non digit. + /// -1 if not found otherwise the index. + public static int IndexOfNonDigit(string str) + { + return IndexOfNonDigit(str, 0); + } + + /// + /// Return the string of digits from string. + /// + /// + /// Source string to search. + public static string SubDigitString(string str) + { + return SubDigitString(str, 0); + } + + + /// + /// Return the string of digits from string. + /// + /// + /// Source string to search. + /// + /// Start index from which to search. + public static string SubDigitString(string str, int idx) + { + string ret = null; + int sidx = IndexOfDigit(str, idx); + if (sidx != -1) + { + int eidx = IndexOfNonDigit(str, sidx); + ret = (eidx == -1) ? str.Substring(sidx) : str.Substring(sidx, eidx - sidx); + } + return ret; + } + + /// + /// Removes the attribute from the source string and returns. + /// + public static string StripXmlAttribute(string src, string attrName) + { + string ret = null; + // quick exit.. + if (src == null) + { + return null; + } + // find the attribute and remove all occurances of it.. + char[] quote = new char[] { '\'', '"' }; + ret = src; + while (true) + { + int start = ret.IndexOf(attrName); + // no more attributes + if (start == -1) + { + break; + } + // find the end of the attribute + int openQuote = IndexOf(ret, quote, start); + // there a problem because there's no open quote.. + if (openQuote == -1) + { + break; + } + // look for the closed quote + int closeQuote = IndexOf(ret, quote, openQuote + 1); + if (closeQuote == -1) + { + break; + } + // remove the space either before or after the attribute + if (start - 1 >= 0 && ret[start - 1] == ' ') + { + start -= 1; + } + else if (closeQuote + 1 < ret.Length && ret[closeQuote + 1] == ' ') + { + closeQuote += 1; + } + // construct new string from parts.. + StringBuilder builder = new StringBuilder(); + builder.Append(ret.Substring(0, start)); + builder.Append(ret.Substring(closeQuote + 1)); + ret = builder.ToString(); + } + return ret; + } + + /// + /// Removes newline characters (0x0a and 0x0d) from a string. + /// + public static string StripNewlines(string src) + { + String dest = null; + if (src != null) + { + StringBuilder b = new StringBuilder(); + int max = src.Length; + for (int i = 0; i < max; i++) + { + char c = src[i]; + if (c != 0x0a && c != 0x0d) + { + b.Append(c); + } + } + dest = b.ToString(); + } + return dest; + } + + /// + /// Finds the start index of the comparison string regards of case. + /// + /// + /// String to search. + /// + /// Comparsion string to find. + /// -1 if not found otherwise the index of the starting character. + public static int IndexOfIgnoreCase(string src, string cmp) + { + // quick check exit... + if (src == null || cmp == null) + { + return -1; + } + string isrc = src.ToUpper(); + string icmp = cmp.ToUpper(); + return isrc.IndexOf(icmp); + } + + + + private const string END_XMLCOMMENT = "-->"; + private const string START_XMLCOMMENT = " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/ConnectorServerService/ConnectorServerService.Designer.cs b/dotnet/framework/ConnectorServerService/ConnectorServerService.Designer.cs new file mode 100755 index 00000000..fd399166 --- /dev/null +++ b/dotnet/framework/ConnectorServerService/ConnectorServerService.Designer.cs @@ -0,0 +1,37 @@ +namespace Org.ForgeRock.OpenICF.Framework.ConnectorServerService +{ + partial class ConnectorServerService + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ServiceName = "OpenICFWebsocketService"; + } + + #endregion + } +} diff --git a/dotnet/framework/ConnectorServerService/ConnectorServerService.cs b/dotnet/framework/ConnectorServerService/ConnectorServerService.cs new file mode 100755 index 00000000..c66d71b7 --- /dev/null +++ b/dotnet/framework/ConnectorServerService/ConnectorServerService.cs @@ -0,0 +1,621 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Specialized; +using System.Configuration; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http.Headers; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.ServiceModel; +using System.ServiceModel.Configuration; +using System.ServiceProcess; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Org.ForgeRock.OpenICF.Common.ProtoBuf; +using Org.ForgeRock.OpenICF.Framework.Remote; +using Org.ForgeRock.OpenICF.Framework.Service.WcfServiceLibrary; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using vtortola.WebSockets; + +namespace Org.ForgeRock.OpenICF.Framework.ConnectorServerService +{ + + #region OpenICFWebsocketService + + public partial class ConnectorServerService : ServiceBase + { + public const string PropKey = "connectorserver.key"; + public const string PropCertificateThumbprint = "connectorserver.certificateThumbprint"; + public const string PropFacadeLifetime = "connectorserver.maxFacadeLifeTime"; + + private Action _closeAction; + + public ConnectorServerService() + { + InitializeComponent(); + } + + public static void InitializeConnectors(AsyncLocalConnectorInfoManager manager) + { + Assembly assembly = Assembly.GetExecutingAssembly(); + FileInfo thisAssemblyFile = new FileInfo(assembly.Location); + DirectoryInfo directory = thisAssemblyFile.Directory; + if (directory == null) throw new ArgumentNullException("directory"); + Environment.CurrentDirectory = directory.FullName; + Trace.TraceInformation("Starting connector server from: " + Environment.CurrentDirectory); + + FileInfo[] files = directory.GetFiles("*.Connector.dll"); + foreach (FileInfo file in files) + { + Assembly lib = Assembly.LoadFrom(file.ToString()); + manager.AddConnectorAssembly(lib); + } + + // also handle connector DLL file names with a version + FileInfo[] versionedFiles = directory.GetFiles("*.Connector-*.dll"); + foreach (FileInfo versionedFile in versionedFiles) + { + Assembly lib = Assembly.LoadFrom(versionedFile.ToString()); + manager.AddConnectorAssembly(lib); + } + } + + protected virtual Uri[] ExtractEndPoint() + { + ServicesSection servicesSection = + ConfigurationManager.GetSection("system.serviceModel/services") as ServicesSection; + + if (servicesSection != null) + { + foreach (ServiceElement service in servicesSection.Services) + { + foreach (ServiceEndpointElement endpoint in service.Endpoints) + { + if (String.Equals(typeof (IWebSocketService).FullName, endpoint.Contract)) + { + return + (from BaseAddressElement baseAddress in service.Host.BaseAddresses + select new Uri(baseAddress.BaseAddress)).ToArray(); + } + } + } + } + throw new Exception("No BaseAddress //TODO fix message"); + } + + public void StartService(string[] args) + { + OnStart(args); + } + + protected override void OnStart(string[] args) + { + try + { + ConnectorFramework connectorFramework = new ConnectorFramework(); + InitializeConnectors(connectorFramework.LocalManager); + + NameValueCollection settings = ConfigurationManager.AppSettings; + + String keyHash = settings.Get(PropKey); + if (keyHash == null) + { + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException( + "Missing required configuration property: " + PropKey); + } + + ClientAuthenticationValidator validator = new ClientAuthenticationValidator(); + validator.Add(new SingleTenantPrincipal(connectorFramework), keyHash); + + String facadeLifeTimeStr = settings.Get(PropFacadeLifetime); + if (facadeLifeTimeStr != null) + { + // _server.MaxFacadeLifeTime = Int32.Parse(facedeLifeTimeStr); + } + + String disableWcf = settings.Get("disableWcf"); + OperatingSystem os = Environment.OSVersion; + if (disableWcf == null && (os.Platform == PlatformID.Win32NT) && + ((os.Version.Major > 6) || ((os.Version.Major == 6) && (os.Version.Minor >= 2)))) + { + ServiceHost host = new ConnectorServiceHost(validator); + host.Open(); + + _closeAction = () => host.Close(); + Trace.TraceInformation("Started WCF connector server"); + } + else + { + Uri[] endpointUri = ExtractEndPoint(); + if (endpointUri == null) + { + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException( + "Missing required baseAddress"); + } + VtortConnectorServiceHost host = new VtortConnectorServiceHost(validator, endpointUri); + host.Open(); + + _closeAction = () => host.Close(); + Trace.TraceInformation("Started WebSocketListener connector server"); + } + } + catch (Exception e) + { + TraceUtil.TraceException("Exception occured starting connector server", e); + throw; + } + } + + public void StopService() + { + OnStop(); + } + + protected override void OnStop() + { + try + { + Trace.TraceInformation("Stopping connector server"); + if (_closeAction != null) + { + _closeAction(); + _closeAction = null; + } + Trace.TraceInformation("Stopped connector server"); + } + catch (Exception e) + { + TraceUtil.TraceException("Exception occured stopping connector server", e); + } + } + } + + #endregion + + #region VtortConnectorServiceHost + + public class VtortConnectorServiceHost + { + private WebSocketListener _listener; + private readonly ClientAuthenticationValidator _validator; + private readonly Uri _endPointUri; + + public VtortConnectorServiceHost(ClientAuthenticationValidator validator, params Uri[] baseAddresses) + { + _validator = validator; + _endPointUri = baseAddresses[0]; + } + + public void Open() + { + IPAddress ipAddress = IPAddress.Any; + if (_endPointUri.IsLoopback) + { + ipAddress = IPAddress.Loopback; + } + else if (!"0.0.0.0".Equals(_endPointUri.DnsSafeHost)) + { + ipAddress = IOUtil.GetIPAddress(_endPointUri.DnsSafeHost); + } + + var options = new WebSocketListenerOptions() + { + NegotiationQueueCapacity = 128, + ParallelNegotiations = 16, + PingTimeout = Timeout.InfiniteTimeSpan, + SubProtocols = new[] {"v1.openicf.forgerock.org"}, + OnHttpNegotiation = (request, response) => + { + var authHeader = request.Headers["Authorization"]; + if (authHeader != null) + { + var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader); + + // RFC 2617 sec 1.2, "scheme" name is case-insensitive + if (authHeaderVal.Scheme.Equals("basic", + StringComparison.OrdinalIgnoreCase) && + authHeaderVal.Parameter != null) + { + var encoding = Encoding.GetEncoding("iso-8859-1"); + var credentials = encoding.GetString(Convert.FromBase64String(authHeaderVal.Parameter)); + + int separator = credentials.IndexOf(':'); + if (separator != -1) + { + string name = credentials.Substring(0, separator); + string password = credentials.Substring(separator + 1); + + var pair = _validator.FindPrincipal(name); + if (null != pair) + { + if (ClientAuthenticationValidator.Verify(pair.Second, password)) + { + request.Items["ConnectionPrincipal"] = pair.First; + } + else + { + Trace.TraceWarning("Incorrect password - username: {0}", name); + response.Status = HttpStatusCode.Forbidden; + } + } + else + { + Trace.TraceWarning("Unknown username: {0}", name); + response.Status = HttpStatusCode.Forbidden; + } + } + else + { + Trace.TraceWarning("Invalid Basic Authorization : {0}", credentials); + response.Status = HttpStatusCode.BadRequest; + } + } + else + { + Trace.TraceWarning("Basic Authorization header expected but found{0}", authHeader); + response.Status = HttpStatusCode.BadRequest; + } + } + else + { + //401 + Realm + response.Status = HttpStatusCode.Unauthorized; + } + } + }; + _listener = new WebSocketListener(new IPEndPoint(ipAddress, _endPointUri.Port), options); + + bool useSsl = String.Equals("https", _endPointUri.Scheme, StringComparison.OrdinalIgnoreCase) || + String.Equals("wss", _endPointUri.Scheme, StringComparison.OrdinalIgnoreCase); + if (useSsl) + { + _listener.ConnectionExtensions.RegisterExtension( + new WebSocketSecureConnectionExtension(GetCertificate())); + } + + var rfc6455 = new vtortola.WebSockets.Rfc6455.WebSocketFactoryRfc6455(_listener); + _listener.Standards.RegisterStandard(rfc6455); + _listener.Start(); + Task.Run((Func) ListenAsync); + } + + public void Close() + { + if (null != _listener) + { + _listener.Stop(); + _validator.Dispose(); + } + } + + protected X509Certificate2 GetCertificate() + { + NameValueCollection settings = ConfigurationManager.AppSettings; + String certificateThumbprint = settings.Get(ConnectorServerService.PropCertificateThumbprint); + if (String.IsNullOrWhiteSpace(certificateThumbprint)) + { + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException( + "Missing required configuration setting: " + ConnectorServerService.PropCertificateThumbprint); + } + + X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine); + try + { + store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + + X509Certificate2 certificate = + store.Certificates.Cast() + .FirstOrDefault( + certificate1 => + String.Equals(certificate1.Thumbprint, certificateThumbprint, + StringComparison.CurrentCultureIgnoreCase)); + if (certificate == null) + { + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException( + "The Certificate can not be found with thumbprint: " + certificateThumbprint); + } + return certificate; + } + finally + { + store.Close(); + } + } + + private async Task ListenAsync() + { + while (_listener.IsStarted) + { + try + { + var websocket = await _listener.AcceptWebSocketAsync(CancellationToken.None) + .ConfigureAwait(false); + if (websocket != null) + { + object value; + websocket.HttpRequest.Items.TryGetValue("ConnectionPrincipal", out value); + ConnectionPrincipal connectionPrincipal = value as ConnectionPrincipal; + if (null != connectionPrincipal) + { + Task.Run(() => HandleWebSocketAsync(websocket, connectionPrincipal)).ConfigureAwait(false); + } + else + { + Trace.TraceInformation("Unidentified WebSocket from {0}", websocket.RemoteEndpoint); + websocket.Close(); + } + } + else + { + Trace.TraceInformation("Stopping Connector Server!?"); + } + } + catch (Exception e) + { + TraceUtil.TraceException("Failed to Accept WebSocket", e); + } + } + } + + private async Task HandleWebSocketAsync(WebSocket websocket, ConnectionPrincipal connectionPrincipal) + { + VtortWebsocket soWebsocket = new VtortWebsocket(websocket, connectionPrincipal); + try + { + Trace.TraceInformation("Server onConnect()"); + connectionPrincipal.OperationMessageListener.OnConnect(soWebsocket); + + while (websocket.IsConnected) + { + var message = await websocket.ReadMessageAsync(CancellationToken.None) + .ConfigureAwait(false); + if (message != null) + { + switch (message.MessageType) + { + case WebSocketMessageType.Text: + using (var sr = new StreamReader(message, Encoding.UTF8)) + { + String msgContent = await sr.ReadToEndAsync(); + connectionPrincipal.OperationMessageListener.OnMessage(soWebsocket, msgContent); + } + break; + case WebSocketMessageType.Binary: + using (var ms = new MemoryStream()) + { + await message.CopyToAsync(ms); + if (ms.Length > 0) + { + connectionPrincipal.OperationMessageListener.OnMessage(soWebsocket, ms.ToArray()); + } + } + break; + } + } + } + } + catch (Exception ex) + { + connectionPrincipal.OperationMessageListener.OnError(ex); + } + finally + { + connectionPrincipal.OperationMessageListener.OnClose(soWebsocket, 1000, ""); + //Dispose before onClose is complete ??? + websocket.Dispose(); + } + } + } + + #endregion + + #region VtortWebsocket + + public class VtortWebsocket : WebSocketConnectionHolder + { + private readonly WebSocket _webSocket; + private RemoteOperationContext _context; + private readonly ConnectionPrincipal _principal; + + public VtortWebsocket(WebSocket webSocket, ConnectionPrincipal principal) + { + _webSocket = webSocket; + _principal = principal; + Task.Factory.StartNew(WriteMessageAsync); + } + + protected override async Task WriteMessageAsync(byte[] entry, + System.Net.WebSockets.WebSocketMessageType messageType) + { + try + { + switch (messageType) + { + case System.Net.WebSockets.WebSocketMessageType.Binary: + using (var writer = _webSocket.CreateMessageWriter(WebSocketMessageType.Binary)) + { + await writer.WriteAsync(entry, 0, entry.Length); + } + break; + case System.Net.WebSockets.WebSocketMessageType.Text: + using (var writer = _webSocket.CreateMessageWriter(WebSocketMessageType.Text)) + { + await writer.WriteAsync(entry, 0, entry.Length); + } + break; + default: + throw new InvalidAttributeValueException("Unsupported WebSocketMessageType: " + messageType); + } + } + catch (Exception e) + { + TraceUtil.TraceException("Failed to write", e); + } + } + + protected override void Handshake(HandshakeMessage message) + { + _context = _principal.Handshake(this, message); + if (null != _context) + { + Trace.TraceInformation("Client Connection Handshake succeeded"); + } + else + { + Trace.TraceError("Client Connection Handshake failed - Close Connection"); + TryClose(); + } + } + + protected override void TryClose() + { + _webSocket.Close(); + } + + public override bool Operational + { + get { return _webSocket.IsConnected; } + } + + public override RemoteOperationContext RemoteConnectionContext + { + get { return _context; } + } + } + + #endregion + + /* + #region BasicAuthHttpModule + + public abstract class BasicAuthHttpModule : IHttpModule + { + private const string Realm = "OpenICF"; + + public void Init(HttpApplication context) + { + // Register event handlers + context.AuthenticateRequest += OnApplicationAuthenticateRequest; + context.EndRequest += OnApplicationEndRequest; + } + + private static void SetPrincipal(IPrincipal principal) + { + Thread.CurrentPrincipal = principal; + if (HttpContext.Current != null) + { + HttpContext.Current.User = principal; + } + } + + protected abstract ConnectionPrincipal CheckPassword(SecurityToken token); + + private void AuthenticateUser(string credentials) + { + try + { + var encoding = Encoding.GetEncoding("iso-8859-1"); + credentials = encoding.GetString(Convert.FromBase64String(credentials)); + + int separator = credentials.IndexOf(':'); + if (separator != -1) + { + string name = credentials.Substring(0, separator); + string password = credentials.Substring(separator + 1); + + UserNameSecurityToken token = new UserNameSecurityToken(name, password); + ConnectionPrincipal principal = CheckPassword(token); + + if (null != principal) + { + SetPrincipal(principal); + } + else + { + // Invalid username or password. + HttpContext.Current.Response.StatusCode = 403; + } + } + else + { + //Bad Request + HttpContext.Current.Response.StatusCode = 400; + } + } + catch (FormatException) + { + // Credentials were not formatted correctly. + HttpContext.Current.Response.StatusCode = 401; + } + } + + private void OnApplicationAuthenticateRequest(object sender, EventArgs e) + { + var request = HttpContext.Current.Request; + var authHeader = request.Headers["Authorization"]; + if (authHeader != null) + { + var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader); + + // RFC 2617 sec 1.2, "scheme" name is case-insensitive + if (authHeaderVal.Scheme.Equals("basic", + StringComparison.OrdinalIgnoreCase) && + authHeaderVal.Parameter != null) + { + AuthenticateUser(authHeaderVal.Parameter); + } + } + else + { + HttpContext.Current.Response.StatusCode = 401; + } + } + + // If the request was unauthorized, add the WWW-Authenticate header + // to the response. + private static void OnApplicationEndRequest(object sender, EventArgs e) + { + var response = HttpContext.Current.Response; + if (response.StatusCode == 401) + { + response.Headers.Add("WWW-Authenticate", + string.Format("Basic realm=\"{0}\"", Realm)); + } + } + + public void Dispose() + { + } + } + + #endregion + */ +} diff --git a/dotnet/framework/ConnectorServerService/ConnectorServerService.csproj b/dotnet/framework/ConnectorServerService/ConnectorServerService.csproj new file mode 100755 index 00000000..e96bfbc9 --- /dev/null +++ b/dotnet/framework/ConnectorServerService/ConnectorServerService.csproj @@ -0,0 +1,154 @@ + + + + + + Debug + AnyCPU + {E5DCC07F-7B42-4AE9-8D6C-A15525476E0A} + Exe + Properties + Org.ForgeRock.OpenICF.Framework.ConnectorServerService + ConnectorServerService + OpenICF Framework - Connector Server Service + v4.5.2 + 512 + + True + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + FR_ICF_sq_med.ico + + + + + ..\packages\Google.ProtocolBuffers.3\lib\Google.Protobuf.dll + + + + + + + + + + False + ..\packages\vtortola.WebSocketListener.2.1.9.0\lib\net45\vtortola.WebSockets.dll + + + False + ..\packages\vtortola.WebSocketListener.2.1.9.0\lib\net45\vtortola.WebSockets.Rfc6455.dll + + + False + ..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + + + + + Component + + + ConnectorServerService.cs + + + + Component + + + + True + True + Resources.resx + + + + + Designer + + + + + + {f140e8da-52b4-4159-992a-9da10ea8eefb} + Common + + + {8b24461b-456a-4032-89a1-cd418f7b5b62} + Framework + + + {5a9e8c5b-4d41-4e3e-9680-6c195bfad47a} + FrameworkProtoBuf + + + {b85c5a35-e3a2-4b04-9693-795e57d66de2} + FrameworkRpc + + + {5b47befd-c60b-4e80-943e-a7151ceea568} + FrameworkServer + + + {D1771E11-C7D3-43FD-9D87-46F1231846F1} + WcfServiceLibrary + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/ConnectorServerService/FR_ICF_sq_med.ico b/dotnet/framework/ConnectorServerService/FR_ICF_sq_med.ico new file mode 100755 index 00000000..10240f47 Binary files /dev/null and b/dotnet/framework/ConnectorServerService/FR_ICF_sq_med.ico differ diff --git a/dotnet/framework/ConnectorServerService/Program.cs b/dotnet/framework/ConnectorServerService/Program.cs new file mode 100755 index 00000000..27d8a4bc --- /dev/null +++ b/dotnet/framework/ConnectorServerService/Program.cs @@ -0,0 +1,274 @@ +using System; +using System.Configuration; +using System.Configuration.Install; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.ServiceProcess; +using Org.ForgeRock.OpenICF.Framework.ConnectorServerService.Properties; +using Org.IdentityConnectors.Common.Security; + +namespace Org.ForgeRock.OpenICF.Framework.ConnectorServerService +{ + internal static class Program + { + private const string Debug = "debug"; + + private static void Usage() + { + Console.WriteLine("Usage: ConnectorServer.exe [option], where command is one of the following: "); + Console.WriteLine(" /install [/serviceName ] - Installs the service."); + Console.WriteLine(" /uninstall [/serviceName ] - Uninstalls the service."); + Console.WriteLine(" /run - Runs the service from the console."); + Console.WriteLine(" /setKey [] - Sets the connector server key."); + Console.WriteLine(" /setCertificate - Sets secure server certificate thumbprint"); + Console.WriteLine(" /setDefaults - Sets default app.config"); + } + + /// + /// The main entry point for the application. + /// + private static void Main(string[] args) + { + if (args.Length == 0) + { + Usage(); + } + else + { + String cmd = args[0].ToLower(); + if (cmd.Equals("/setkey", StringComparison.InvariantCultureIgnoreCase)) + { + if (args.Length > 2) + { + Usage(); + return; + } + DoSetKey(args.Length > 1 ? args[1] : null); + return; + } + if (cmd.Equals("/setCertificate", StringComparison.InvariantCultureIgnoreCase)) + { + if (args.Length > 1) + { + Usage(); + return; + } + DoSetCertificate(); + return; + } + if (cmd.Equals("/setDefaults", StringComparison.InvariantCultureIgnoreCase)) + { + if (args.Length > 1) + { + Usage(); + return; + } + using ( + var file = new StreamWriter(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, false)) + { + file.WriteLine(Resources.ResourceManager.GetString("DefaultConfig")); + Console.WriteLine(@"Default configuration successfully restored."); + } + return; + } + if ("/install".Equals(cmd, StringComparison.InvariantCultureIgnoreCase)) + { + DoInstall(); + } + else if ("/uninstall".Equals(cmd, StringComparison.InvariantCultureIgnoreCase)) + { + DoUninstall(); + } + else if ("/run".Equals(cmd, StringComparison.InvariantCultureIgnoreCase)) + { + if (args.Length > 1 && Debug.Equals(args[1], StringComparison.InvariantCultureIgnoreCase)) + { + Process currentProcess = Process.GetCurrentProcess(); + Console.WriteLine( + @"It's time to attach with debugger to process:{0} and press any key to continue.", + currentProcess.Id); + Console.ReadKey(); + } + DoRun(); + } + else if ("/service".Equals(cmd, StringComparison.InvariantCultureIgnoreCase)) + { + ServiceBase.Run(new ServiceBase[] {new ConnectorServerService()}); + } + else + { + Usage(); + } + } + } + + private static void DoInstall() + { + TransactedInstaller ti = new TransactedInstaller(); + string[] cmdline = + { + Assembly.GetExecutingAssembly().Location + }; + AssemblyInstaller ai = new AssemblyInstaller( + cmdline[0], + new string[0]); + ti.Installers.Add(ai); + InstallContext ctx = new InstallContext("install.log", + cmdline); + ti.Context = ctx; + ti.Install(new System.Collections.Hashtable()); + } + + private static void DoUninstall() + { + TransactedInstaller ti = new TransactedInstaller(); + string[] cmdline = + { + Assembly.GetExecutingAssembly().Location + }; + AssemblyInstaller ai = new AssemblyInstaller( + cmdline[0], + new string[0]); + ti.Installers.Add(ai); + InstallContext ctx = new InstallContext("uninstall.log", + cmdline); + ti.Context = ctx; + ti.Uninstall(null); + } + + private static void DoRun() + { + ConnectorServerService svc = new ConnectorServerService(); + + svc.StartService(new String[0]); + + Console.WriteLine(@"Press q to shutdown."); + + while (true) + { + ConsoleKeyInfo info = Console.ReadKey(); + if (info.KeyChar == 'q') + { + break; + } + } + svc.StopService(); + } + + private static GuardedString ReadPassword() + { + GuardedString rv = new GuardedString(); + while (true) + { + ConsoleKeyInfo info = Console.ReadKey(true); + if (info.Key == ConsoleKey.Enter) + { + Console.WriteLine(); + rv.MakeReadOnly(); + return rv; + } + else + { + Console.Write("*"); + rv.AppendChar(info.KeyChar); + } + } + } + + private static void DoSetCertificate() + { + X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine); + try + { + store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + X509Certificate2Collection certificates = store.Certificates; + int i = 0; + if (certificates.Count > 0) + { + Console.WriteLine(@"Select certificate you want to use:"); + Console.WriteLine(@"Index Issued To Thumbprint"); + Console.WriteLine(@"----- --------- -------------------------"); + Console.WriteLine(); + foreach (var cerItem in certificates) + { + Console.WriteLine(@"{0,4}) {1,-25} {2}", i++, + cerItem.GetNameInfo(X509NameType.SimpleName, false), + cerItem.Thumbprint); + } + string line; + Console.WriteLine(); + do + { + line = Console.ReadLine(); + if (!String.IsNullOrWhiteSpace(line)) + { + try + { + int inputIndex = Convert.ToInt32(line); + if (inputIndex >= 0 && inputIndex < certificates.Count) + { + X509Certificate2 certificate = store.Certificates[inputIndex]; + Configuration config = + ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + config.AppSettings.Settings.Remove(ConnectorServerService.PropCertificateThumbprint); + config.AppSettings.Settings.Add(ConnectorServerService.PropCertificateThumbprint, + certificate.Thumbprint); + config.Save(ConfigurationSaveMode.Modified); + Console.WriteLine(@"Certificate Thumbprint has been successfully updated to {0}.", + certificate.Thumbprint); + break; + } + } + catch (FormatException) + { + } + Console.WriteLine(@"Invalid input: {0}", line); + } + } while (!String.IsNullOrWhiteSpace(line)); + } + else + { + Console.WriteLine(@"No certificate was found in 'LocalMachine:My' store"); + } + } + finally + { + store.Close(); + } + } + + private static void DoSetKey(string key) + { + GuardedString str; + if (key == null) + { + Console.Write("Please enter the new key: "); + GuardedString v1 = ReadPassword(); + Console.Write("Please confirm the new key: "); + GuardedString v2 = ReadPassword(); + if (!v1.Equals(v2)) + { + Console.WriteLine("Error: Key mismatch."); + return; + } + str = v2; + } + else + { + str = new GuardedString(); + foreach (char c in key) + { + str.AppendChar(c); + } + } + Configuration config = + ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + config.AppSettings.Settings.Remove(ConnectorServerService.PropKey); + config.AppSettings.Settings.Add(ConnectorServerService.PropKey, str.GetBase64SHA1Hash()); + config.Save(ConfigurationSaveMode.Modified); + Console.WriteLine("Key has been successfully updated."); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/ConnectorServerService/ProjectInstaller.cs b/dotnet/framework/ConnectorServerService/ProjectInstaller.cs new file mode 100755 index 00000000..cf7b2df8 --- /dev/null +++ b/dotnet/framework/ConnectorServerService/ProjectInstaller.cs @@ -0,0 +1,93 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014-2015 ForgeRock AS. + */ + +using System; +using System.ComponentModel; +using System.Configuration.Install; +using System.Diagnostics; +using System.IO; +using System.ServiceProcess; + +namespace Org.ForgeRock.OpenICF.Framework.ConnectorServerService +{ + [RunInstaller(true)] + public class ProjectInstaller : Installer + { + public static string ServiceName { get; set; } + public static string DisplayName { get; set; } + public static string Description { get; set; } + + static ProjectInstaller() + { + ServiceName = "ConnectorServerService"; + DisplayName = "OpenICF Connector Server"; + Description = "OpenICF Connector Server"; + } + + public ProjectInstaller() + { + Process pc = Process.GetCurrentProcess(); + Directory.SetCurrentDirectory + (pc.MainModule.FileName.Substring(0, + pc.MainModule.FileName.LastIndexOf(@"\", + StringComparison.CurrentCulture) + )); + + var serviceProcessInstaller = new ServiceProcessInstaller(); + var serviceInstaller = new ServiceInstaller(); + + // Here you can set properties on serviceProcessInstaller or register event handlers + serviceProcessInstaller.Account = ServiceAccount.LocalSystem; + + serviceInstaller.ServiceName = ServiceName; + serviceInstaller.Description = Description; + serviceInstaller.DisplayName = DisplayName; + serviceInstaller.StartType = ServiceStartMode.Automatic; + + Installers.AddRange(new Installer[] {serviceProcessInstaller, serviceInstaller}); + } + + protected string AppendPathParameter(string path, string parameter) + { + if (path.Length > 0 && path[0] != '"') + { + path = "\"" + path + "\""; + } + path += " " + parameter; + return path; + } + + protected override void OnBeforeInstall(System.Collections.IDictionary savedState) + { + Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service"); + base.OnBeforeInstall(savedState); + } + + protected override void OnBeforeUninstall(System.Collections.IDictionary savedState) + { + Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service"); + base.OnBeforeUninstall(savedState); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/ConnectorServerService/Properties/Resources.Designer.cs b/dotnet/framework/ConnectorServerService/Properties/Resources.Designer.cs new file mode 100755 index 00000000..8c5177ed --- /dev/null +++ b/dotnet/framework/ConnectorServerService/Properties/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34209 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Org.ForgeRock.OpenICF.Framework.ConnectorServerService.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Org.ForgeRock.OpenICF.Framework.ConnectorServerService.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<configuration> + /// <appSettings> + /// <!-- Access these values via the property: + /// System.Configuration.ConfigurationManager.AppSettings[key] + /// --> + /// <add key="connectorserver.certificateThumbprint" value="Use certutil and copy: Cert Hash(sha1) Example:1b0889cdf9e0cee904646bb8a3d0aa4f72035056" /> + /// <add key="connectorserver.maxFacadeLifeTime" value="0" /> + /// <add key="connectorserver.key" value="lmA6bMfENJGlIDbfrVtklXFK32s=" /> + /// <!-- Enable/ [rest of string was truncated]";. + /// + internal static string DefaultConfig { + get { + return ResourceManager.GetString("DefaultConfig", resourceCulture); + } + } + } +} diff --git a/dotnet/framework/ConnectorServerService/Properties/Resources.resx b/dotnet/framework/ConnectorServerService/Properties/Resources.resx new file mode 100755 index 00000000..e65057e9 --- /dev/null +++ b/dotnet/framework/ConnectorServerService/Properties/Resources.resx @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <?xml version="1.0" encoding="utf-8"?> +<configuration> + <appSettings> + <!-- Access these values via the property: + System.Configuration.ConfigurationManager.AppSettings[key] + --> + <add key="connectorserver.certificateThumbprint" value="Use certutil and copy: Cert Hash(sha1) Example:1b0889cdf9e0cee904646bb8a3d0aa4f72035056" /> + <add key="connectorserver.maxFacadeLifeTime" value="0" /> + <add key="connectorserver.key" value="lmA6bMfENJGlIDbfrVtklXFK32s=" /> + <!-- Enable/Disable the logging proxy for all operations. --> + <add key="logging.proxy" value="false" /> + <add key="disableWcf" value="true" /> + </appSettings> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> + </startup> + <system.serviceModel> + <services> + <service name="Org.ForgeRock.OpenICF.Framework.Service.WcfServiceLibrary.WcfWebsocket"> + <endpoint address="" binding="customBinding" bindingConfiguration="customWebSocket" + contract="Org.ForgeRock.OpenICF.Framework.Service.WcfServiceLibrary.IWebSocketService"> + <identity> + <dns value="localhost" /> + </identity> + </endpoint> + <host> + <baseAddresses> + <!-- Remove trailing '/' otherwise response: 405 Method Not Allowed --> + <add baseAddress="http://0.0.0.0:8759/openicf" /> + </baseAddresses> + </host> + </service> + </services> + <bindings> + <customBinding> + <binding name="customWebSocket"> + <byteStreamMessageEncoding /> + <httpTransport authenticationScheme="Basic" realm="OpenICF"> + <webSocketSettings transportUsage="Always" createNotificationOnConnection="true" + subProtocol="v1.openicf.forgerock.org" /> + </httpTransport> + </binding> + </customBinding> + </bindings> + <behaviors> + <serviceBehaviors> + <behavior> + <!-- To avoid disclosing metadata information, set the values below to false before deployment --> + <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false" /> + <!-- To receive exception details in faults for debugging purposes, set the value below to true. + Set to false before deployment to avoid disclosing exception information --> + <serviceDebug includeExceptionDetailInFaults="false" /> + <!--Specify the Certificate- -> + <serviceCertificate findValue="ConnectorServerSSLCertificate" + storeLocation="LocalMachine" + x509FindType="FindBySubjectName" + storeName="ConnectorServerSSLCertificate" /--> + </behavior> + </serviceBehaviors> + </behaviors> + <!--serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" /--> + </system.serviceModel> + <system.diagnostics> + <trace autoflush="true" indentsize="4"> + <listeners> + <remove name="Default" /> + <add name="console" /> + <add name="file" /> + </listeners> + </trace> + <sources> + <source name="ConnectorServer" switchName="switch1"> + <listeners> + <remove name="Default" /> + <add name="file" /> + </listeners> + </source> + </sources> + <switches> + <add name="switch1" value="Information" /> + </switches> + <sharedListeners> + <add name="console" type="System.Diagnostics.ConsoleTraceListener" /> + <add name="file" type="System.Diagnostics.TextWriterTraceListener" initializeData="logs\ConnectorServerService.log" + traceOutputOptions="DateTime"> + <filter type="System.Diagnostics.EventTypeFilter" initializeData="Information" /> + </add> + </sharedListeners> + </system.diagnostics> +</configuration> + + \ No newline at end of file diff --git a/dotnet/framework/ConnectorServerService/packages.config b/dotnet/framework/ConnectorServerService/packages.config new file mode 100755 index 00000000..40276061 --- /dev/null +++ b/dotnet/framework/ConnectorServerService/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dotnet/framework/ConnectorServerService/version.template b/dotnet/framework/ConnectorServerService/version.template new file mode 100755 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/ConnectorServerService/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/Framework.targets b/dotnet/framework/Framework.targets new file mode 100644 index 00000000..a23074b5 --- /dev/null +++ b/dotnet/framework/Framework.targets @@ -0,0 +1,105 @@ + + + + $(MSBuildProjectDirectory)\version.template + $(MSBuildProjectDirectory)\version.txt + ForgeRock + Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + $(MSBuildProjectDirectory)\..\Dist + + $(MSBuildProjectDirectory) + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FrameworkBeforeBuild; + $(BuildDependsOn); + FrameworkAfterBuild + + + $(CleanDependsOn); + FrameworkClean + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/Framework/Api.cs b/dotnet/framework/Framework/Api.cs new file mode 100644 index 00000000..ec44e054 --- /dev/null +++ b/dotnet/framework/Framework/Api.cs @@ -0,0 +1,940 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using System.Net.Security; +using System.Text; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Pooling; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; + +namespace Org.IdentityConnectors.Framework.Api +{ + public static class APIConstants + { + public const int NO_TIMEOUT = -1; + } + + #region APIConfiguration + /// + /// Interface to show the configuration properties from both the SPI/API based on + /// the makeup. + /// + /// Before this is passed into the one must call + /// and configure accordingly. + /// + public interface APIConfiguration + { + + /// + /// Gets instance of the configuration properties. + /// + /// These are initialized to their default values based on meta information. + /// Caller can then modify the properties as needed. + /// + ConfigurationProperties ConfigurationProperties { get; } + + /// + /// Add a configuration change listener callback handler. + /// + /// This callback handler will be notified when connector push an event back + /// to application to notify the initial configuration has to be changed + /// before next time creating a new Connectorfacade in order to continue + /// operate properly. + /// + /// + /// the callback handler to receive the change event. + /// a closeable to unregister the change listener. + IConfigurationPropertyChangeListener ChangeListener { get; set; } + + /// + /// Determines if this uses the framework's connector + /// pooling. + /// + /// true if the uses the framework's connector + /// pooling feature. + bool IsConnectorPoolingSupported { get; } + + /// + /// Gets the connector pooling configuration. + /// + /// This is initialized to the default values. Caller can then modify the + /// properties as needed. + /// + ObjectPoolConfiguration ConnectorPoolConfiguration { get; } + + // ======================================================================= + // Operational Support Set + // ======================================================================= + /// + /// Get the set of operations that this will support. + /// + ICollection> SupportedOperations { get; } + + // ======================================================================= + // Framework Configuration.. + // ======================================================================= + /// + /// Sets the timeout value for the operation provided. + /// + /// + /// particular operation that requires a timeout. + /// + /// milliseconds that the operation will wait in order to + /// complete. Values less than or equal to zero are considered to + /// disable the timeout property. + void SetTimeout(SafeType operation, int timeout); + + /// + /// Gets the timeout in milliseconds based on the operation provided. + /// + /// + /// particular operation to get a timeout for. + /// milliseconds to wait for an operation to complete before throwing + /// an error. + int GetTimeout(SafeType operation); + + /// + /// Sets the size of the buffer for the support + /// and what the results of the producer buffered. + /// + /// + /// default is 100, if size is set to zero or less will disable + /// buffering. + int ProducerBufferSize { get; set; } + + + /// + /// Get the configuration of the ResultsHandler chain of the Search + /// operation. + /// + ResultsHandlerConfiguration ResultsHandlerConfiguration { get; } + + } + #endregion + + #region ConfigurationProperties + /// + /// Configuration properties encapsulates the and uses + /// to determine the properties available for manipulation. + /// + public interface ConfigurationProperties + { + /// + /// Get the list of properties names for this . + /// + /// get the list of properties names. + IList PropertyNames { get; } + + /// + /// Get a particular by name. + /// + /// the unique name of the property. + /// a if it exists otherwise null. + ConfigurationProperty GetProperty(string name); + + /// + /// Set the value of the property by name. + /// + /// Name of the property to set the value against. + /// Value to set on the configuration property. + /// iff the property name does not exist. + void SetPropertyValue(string name, Object value); + + } + #endregion + + #region ConfigurationProperty + /// + /// Translation from at the SPI layer to the API. + /// + public interface ConfigurationProperty + { + int Order { get; } + + /// + /// Get the unique name of the configuration property. + /// + string Name { get; } + + /// + /// Get the help message from the message catalog. + /// + string GetHelpMessage(string def); + + /// + /// Get the display name for this configuration property. + /// + string GetDisplayName(string def); + + /// + /// Get name of the group for this configuration property. + /// + string GetGroup(string def); + + /// + /// Get the value from the property. + /// + /// + /// This should be the default value. + /// + object Value { get; set; } + + /// + /// Get the type of the property. + /// + Type ValueType { get; } + + /// + /// Is this a confidential property whose value should be encrypted by + /// the application when persisted? + /// + bool IsConfidential { get; } + + /// + /// Is this a required property + /// + /// True if the property is required + bool IsRequired { get; } + + /// + /// Set of operations for which this property must be specified. + /// + /// + /// This is used for the case where a connector may or may not + /// implement certain operations depending in the configuration. + /// The default value of "empty array" is special in that + /// it means that this property is applicable to all operations. + /// + /// + ICollection> Operations { get; } + } + #endregion + + #region IConfigurationPropertyChangeListener + /// + /// A ConfigurationPropertyChangeListener receives the change of + /// ConfigurationProperty. + /// + /// since 1.5 + public interface IConfigurationPropertyChangeListener + { + + /// + /// Receives notification that an ConfigurationProperty has been changed to + /// connector. + /// + /// + /// containing the property name and value of the + /// ConfigurationProperty that was changed. + void ConfigurationPropertyChange(IList changes); + + } + #endregion + + #region ConnectorFacade + /// + /// Main interface through which an application invokes Connector operations. + /// Represents at the API level a specific instance of a Connector that has been + /// configured in a specific way. + /// + /// + public interface ConnectorFacade : CreateApiOp, IConnectorEventSubscriptionApiOp, DeleteApiOp, + SearchApiOp, UpdateApiOp, SchemaApiOp, AuthenticationApiOp, ResolveUsernameApiOp, + GetApiOp, ValidateApiOp, TestApiOp, ScriptOnConnectorApiOp, ScriptOnResourceApiOp, + ISyncEventSubscriptionApiOp, SyncApiOp + { + + /// + /// Gets the unique generated identifier of this ConnectorFacade. + /// + /// It's not guarantied that the equivalent configuration will generate the + /// same configuration key. Always use the generated value and maintain it in + /// the external application. + /// + /// identifier of this ConnectorFacade instance. + /// Since 1.4 + string ConnectorFacadeKey { get; } + + /// + /// Get the set of operations that this will support. + /// + ICollection> SupportedOperations { get; } + + /// + /// Get an instance of an operation that this facade supports. + /// + APIOperation GetOperation(SafeType type); + + } + #endregion + + #region ConnectorFacadeFactory + /// + /// Manages a pool of connectors for use by a provisioner. + /// + public abstract class ConnectorFacadeFactory + { + // At some point we might make this pluggable, but for now, hard-code + private const string IMPL_NAME = + "Org.IdentityConnectors.Framework.Impl.Api.ConnectorFacadeFactoryImpl"; + + private const string IMPL_NAME_MANAGED = + "Org.IdentityConnectors.Framework.Impl.Api.ManagedConnectorFacadeFactoryImpl"; + + private static ConnectorFacadeFactory _instance; + private static ConnectorFacadeFactory _managedInstance; + private static object LOCK = new Object(); + + /// + /// Get the singleton instance of the . + /// + public static ConnectorFacadeFactory GetInstance() + { + lock (LOCK) + { + if (_instance == null) + { + SafeType t = FrameworkInternalBridge.LoadType(IMPL_NAME); + _instance = t.CreateInstance(); + } + } + return _instance; + } + + /// + /// Get the singleton instance of the stateful . + /// + /// Since 1.4 + public static ConnectorFacadeFactory GetManagedInstance() + { + lock (LOCK) + { + if (_managedInstance == null) + { + SafeType t = FrameworkInternalBridge.LoadType(IMPL_NAME_MANAGED); + _managedInstance = t.CreateInstance(); + } + return _managedInstance; + } + + } + + /// + /// Get a new instance of . + /// + /// all the configuration that the framework, connector, and + /// pooling needs. + /// + /// to call API operations against. + /// + public abstract ConnectorFacade NewInstance(APIConfiguration config); + + /// + /// Get a new instance of . + /// + /// TODO add doc later + /// all the configuration that the framework, connector, and pooling needs. It's a Base64 serialised APIConfiguration instance. + /// + /// to call API operations against. + /// + /// since 1.4 + public abstract ConnectorFacade NewInstance(ConnectorInfo connectorInfo, String config); + + /// + /// Dispose of all connection pools, resources, etc. + /// + public abstract void Dispose(); + } + #endregion + + #region ConnectorInfo + /// + /// The connector meta-data for a given connector. + /// + public interface ConnectorInfo + { + /// + /// Returns a friendly name suitable for display in the UI. + /// + /// The friendly name + string GetConnectorDisplayName(); + + ConnectorMessages Messages { get; } + + ConnectorKey ConnectorKey { get; } + + /// + /// Loads the and class in order to + /// determine the proper default configuration parameters. + /// + APIConfiguration CreateDefaultAPIConfiguration(); + } + #endregion + + #region ConnectorInfoManager + /// + /// Class responsible for maintaing a list of ConnectorInfo + /// associated with a set of connector bundles. + /// + public interface ConnectorInfoManager + { + /// + /// Returns the list of ConnectorInfo + /// + /// the list of ConnectorInfo + IList ConnectorInfos { get; } + + /// + /// Given a connectorName and connectorVersion, returns the + /// associated ConnectorInfo. + /// + /// The connector key. + /// The ConnectorInfo or null if it couldn't + /// be found. + ConnectorInfo FindConnectorInfo(ConnectorKey key); + } + #endregion + + #region ConnectorInfoManagerFactory + /// + /// The main entry point into connectors. + /// + /// + /// This allows you + /// to load the connector classes from a set of bundles. + /// + public abstract class ConnectorInfoManagerFactory + { + //At some point we might make this pluggable, but for now, hard-code + private const string IMPL_NAME = + "Org.IdentityConnectors.Framework.Impl.Api.ConnectorInfoManagerFactoryImpl"; + private static ConnectorInfoManagerFactory _instance; + private static object LOCK = new Object(); + /// + /// Singleton pattern for getting an instance of the + /// ConnectorInfoManagerFactory. + /// + /// + public static ConnectorInfoManagerFactory GetInstance() + { + lock (LOCK) + { + if (_instance == null) + { + SafeType t = + FrameworkInternalBridge.LoadType(IMPL_NAME); + _instance = t.CreateInstance(); + } + } + return _instance; + } + public abstract ConnectorInfoManager GetLocalManager(); + public abstract ConnectorInfoManager GetRemoteManager(RemoteFrameworkConnectionInfo info); + + /// + /// Clears the bundle manager cache. + /// + /// + /// Generally intended for unit testing + /// + public abstract void ClearRemoteCache(); + } + #endregion + + #region ConnectorKey + /// + /// Uniquely identifies a connector within an installation. + /// + /// + /// Consists of the triple (bundleName, bundleVersion, connectorName) + /// + public sealed class ConnectorKey + { + private readonly string _bundleName; + private readonly string _bundleVersion; + private readonly string _connectorName; + + public ConnectorKey(String bundleName, + String bundleVersion, + String connectorName) + { + if (bundleName == null) + { + throw new ArgumentException("bundleName may not be null"); + } + if (bundleVersion == null) + { + throw new ArgumentException("bundleVersion may not be null"); + } + if (connectorName == null) + { + throw new ArgumentException("connectorName may not be null"); + } + _bundleName = bundleName; + _bundleVersion = bundleVersion; + _connectorName = connectorName; + } + + public string BundleName + { + get + { + return _bundleName; + } + } + + public string BundleVersion + { + get + { + return _bundleVersion; + } + } + + public string ConnectorName + { + get + { + return _connectorName; + } + } + + public override bool Equals(object o) + { + if (o is ConnectorKey) + { + ConnectorKey other = (ConnectorKey)o; + if (!_bundleName.Equals(other._bundleName)) + { + return false; + } + if (!_bundleVersion.Equals(other._bundleVersion)) + { + return false; + } + if (!_connectorName.Equals(other._connectorName)) + { + return false; + } + return true; + } + return false; + } + + public override int GetHashCode() + { + int rv = 0; + rv ^= _connectorName.GetHashCode(); + return rv; + } + + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + builder.Append("ConnectorKey("); + builder.Append(" bundleName=").Append(_bundleName); + builder.Append(" bundleVersion=").Append(_bundleVersion); + builder.Append(" connectorName=").Append(_connectorName); + builder.Append(" )"); + return builder.ToString(); + } + } + #endregion + + #region RemoteFrameworkConnectionInfo + public sealed class RemoteFrameworkConnectionInfo + { + private readonly String _host; + private readonly int _port; + private readonly GuardedString _key; + private readonly bool _useSSL; + private readonly RemoteCertificateValidationCallback _certificateValidationCallback; + private readonly int _timeout; + + /// + /// Creates a new instance of RemoteFrameworkConnectionInfo, using + /// a clear (non-ssl) connection and a 60-second timeout. + /// + /// The host to connect to + /// The port to connect to + public RemoteFrameworkConnectionInfo(String host, + int port, + GuardedString key) + : this(host, port, key, false, null, 60 * 1000) + { + } + + /// + /// Creates a new instance of RemoteFrameworkConnectionInfo. + /// + /// The host to connect to + /// The port to connect to + /// Set to true if we are to connect via SSL. + /// to use + /// for establising the SSL connection. May be null or empty, + /// in which case the default installed providers for the JVM will + /// be used. Ignored if 'useSSL' is false. + /// The timeout to use (in milliseconds). A value of 0 + /// means infinite timeout; + public RemoteFrameworkConnectionInfo(String host, + int port, + GuardedString key, + bool useSSL, + RemoteCertificateValidationCallback certificateValidationCallback, + int timeout) + { + + if (host == null) + { + throw new ArgumentException("Parameter 'host' is null."); + } + if (key == null) + { + throw new ArgumentException("Parameter 'key' is null."); + } + + _host = host; + _port = port; + _key = key; + _useSSL = useSSL; + _certificateValidationCallback = certificateValidationCallback; + _timeout = timeout; + } + + /// + /// Returns the host to connect to. + /// + /// The host to connect to. + public String Host + { + get + { + return _host; + } + } + + /// + /// Returns the port to connect to + /// + /// The port to connect to + public int Port + { + get + { + return _port; + } + } + + public GuardedString Key + { + get + { + return _key; + } + } + + /// + /// Returns true iff we are to use SSL to connect. + /// + /// true iff we are to use SSL to connect. + public bool UseSSL + { + get + { + return _useSSL; + } + } + + /// + /// Returns the list of 's. + /// + /// + /// to use when establishing + /// the connection. + /// + /// The list of 's. + public RemoteCertificateValidationCallback CertificateValidationCallback + { + get + { + return _certificateValidationCallback; + } + } + + /// + /// Returns the timeout (in milliseconds) to use for the connection. + /// + /// + /// A value of zero means infinite timeout. + /// + /// the timeout (in milliseconds) to use for the connection. + public int Timeout + { + get + { + return _timeout; + } + } + + public override bool Equals(Object o) + { + if (o is RemoteFrameworkConnectionInfo) + { + RemoteFrameworkConnectionInfo other = + (RemoteFrameworkConnectionInfo)o; + if (!Object.Equals(Host, other.Host)) + { + return false; + } + if (Port != other.Port) + { + return false; + } + if (UseSSL != other.UseSSL) + { + return false; + } + if (CertificateValidationCallback == null || + other.CertificateValidationCallback == null) + { + if (CertificateValidationCallback != null || + other.CertificateValidationCallback != null) + { + return false; + } + } + else + { + if (!CertificateValidationCallback.Equals + (other.CertificateValidationCallback)) + { + return false; + } + } + + if (!Key.Equals(other.Key)) + { + return false; + } + + if (Timeout != other.Timeout) + { + return false; + } + + return true; + } + return false; + } + + public override int GetHashCode() + { + return _host.GetHashCode() ^ _port; + } + + public override String ToString() + { + return "{host=" + _host + ", port=" + _port + "}"; + } + } + #endregion + + #region ResultsHandlerConfiguration + /// + /// Configuration for result handler chain + /// + public sealed class ResultsHandlerConfiguration + { + + /// + /// Enables the {@link NormalizingResultsHandler} in the handler chain. + /// + private bool _enableNormalizingResultsHandler = true; + + /// + /// Enables the {@link FilteredResultsHandler} in the handler chain. + /// + private bool _enableFilteredResultsHandler = true; + + /// + /// Enables the case insensitive filtering. + /// + /// + /// + /// + private bool _enableCaseInsensitiveFilter = false; + + /// + /// Enables the {@link AttributesToGetSearchResultsHandler} in the handler chain. + /// + /// + /// + /// + private bool _enableAttributesToGetSearchResultsHandler = true; + + /// + /// Default empty constructor. + /// + public ResultsHandlerConfiguration() + { + } + + /// + /// Copy constructor + /// + /// configuration that copied to. + public ResultsHandlerConfiguration(ResultsHandlerConfiguration source) + { + this.EnableNormalizingResultsHandler = source.EnableNormalizingResultsHandler; + this.EnableFilteredResultsHandler = source.EnableFilteredResultsHandler; + this.EnableCaseInsensitiveFilter = source.EnableCaseInsensitiveFilter; + this.EnableAttributesToGetSearchResultsHandler = source.EnableAttributesToGetSearchResultsHandler; + } + + /// + /// Get the set number of maximum objects (idle+active) + /// + public bool EnableNormalizingResultsHandler + { + get + { + return _enableNormalizingResultsHandler; + } + set + { + _enableNormalizingResultsHandler = value; + } + } + + /// + /// Get the maximum number of idle objects. + /// + public bool EnableFilteredResultsHandler + { + get + { + return _enableFilteredResultsHandler; + } + set + { + _enableFilteredResultsHandler = value; + } + } + + /// + /// Max time to wait if the pool is waiting for a free object to become + /// available before failing. + /// + /// + /// Zero means don't wait + /// + public bool EnableCaseInsensitiveFilter + { + get + { + return _enableCaseInsensitiveFilter; + } + set + { + _enableCaseInsensitiveFilter = value; + } + } + + /// + /// Minimum time to wait before evicting an idle object. + /// + /// + /// Zero means don't wait + /// + public bool EnableAttributesToGetSearchResultsHandler + { + get + { + return _enableAttributesToGetSearchResultsHandler; + } + set + { + _enableAttributesToGetSearchResultsHandler = value; + } + } + + public override int GetHashCode() + { + unchecked + { + int hash = 3; + hash = 79 * hash + (EnableNormalizingResultsHandler ? 1 : 0); + hash = 79 * hash + (EnableFilteredResultsHandler ? 1 : 0); + hash = 79 * hash + (EnableCaseInsensitiveFilter ? 1 : 0); + hash = 79 * hash + (EnableAttributesToGetSearchResultsHandler ? 1 : 0); + return hash; + } + } + + public override bool Equals(Object obj) + { + if (obj is ResultsHandlerConfiguration) + { + ResultsHandlerConfiguration other = (ResultsHandlerConfiguration)obj; + + if (EnableNormalizingResultsHandler != other.EnableNormalizingResultsHandler) + { + return false; + } + if (EnableFilteredResultsHandler != other.EnableFilteredResultsHandler) + { + return false; + } + if (EnableCaseInsensitiveFilter != other.EnableCaseInsensitiveFilter) + { + return false; + } + if (EnableAttributesToGetSearchResultsHandler != other.EnableAttributesToGetSearchResultsHandler) + { + return false; + } + return true; + } + return false; + } + + public override String ToString() + { + // poor man's toString() + IDictionary bld = new Dictionary(); + bld["EnableNormalizingResultsHandler"] = EnableNormalizingResultsHandler; + bld["EnableFilteredResultsHandler"] = EnableFilteredResultsHandler; + bld["EnableCaseInsensitiveFilter"] = EnableCaseInsensitiveFilter; + bld["EnableAttributesToGetSearchResultsHandler"] = EnableAttributesToGetSearchResultsHandler; + return bld.ToString(); + } + } + #endregion +} diff --git a/dotnet/framework/Framework/ApiOperations.cs b/dotnet/framework/Framework/ApiOperations.cs new file mode 100644 index 00000000..fa93a059 --- /dev/null +++ b/dotnet/framework/Framework/ApiOperations.cs @@ -0,0 +1,560 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using System.Collections.Generic; + +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; + +namespace Org.IdentityConnectors.Framework.Api.Operations +{ + /// + /// Base interface for all API operations. + /// + public interface APIOperation + { + } + + public interface AuthenticationApiOp : APIOperation + { + /// + /// Most basic authentication available. + /// + /// + /// string that represents the account or user id. + /// string that represents the password for the account or user. + /// + /// iff the credentials do not pass authentication otherwise + /// nothing. + Uid Authenticate(ObjectClass objectClass, string username, GuardedString password, OperationOptions options); + } + + #region IConnectorEventSubscriptionApiOp + /// + /// since 1.5 + public interface IConnectorEventSubscriptionApiOp : APIOperation + { + ISubscription Subscribe(ObjectClass objectClass, Filter eventFilter, IObserver handler, OperationOptions operationOptions); + } + #endregion + + public interface ResolveUsernameApiOp : APIOperation + { + /// + /// Resolve the given username + /// to the corresponding . + /// + /// + /// The Uid is the one + /// that would return + /// in case of a successful authentication. + /// + /// The object class to use for authenticate. + /// Will typically be an account. Must not be null. + /// string that represents the account or user id. + /// additional options that impact the way this operation is run. + /// May be null. + /// Uid The uid of the account that would be used to authenticate. + /// iff the username could not be resolved. + Uid ResolveUsername(ObjectClass objectClass, string username, OperationOptions options); + } + + /// + /// Operation to create connector objects based on the attributes provided. + /// + public interface CreateApiOp : APIOperation + { + /// + /// Creates a user based on the ConnectorAttributes provide. The only + /// required attribute is the ObjectClass and those required by the + /// Connector. The API will validate the existence of the + /// ObjectClass attribute and that there are no duplicate name'd + /// attributes. + /// + /// + /// ConnectorAttribtes to create the object. + /// Unique id for the created object. + Uid Create(ObjectClass objectClass, ICollection attrs, OperationOptions options); + } + + /// + /// Deletes an object with the specified Uid and ObjectClass on the + /// resource. + /// + public interface DeleteApiOp : APIOperation + { + /// + /// Delete the object that the specified Uid identifies (if any). + /// + /// The type of object to delete. + /// The unique identitfier for the object to delete. + /// Throws UnknowUid if the object does not exist. + void Delete(ObjectClass objectClass, Uid uid, OperationOptions options); + } + + /// + /// Get a particular based on the . + /// + public interface GetApiOp : APIOperation + { + /// + /// Get a particular based on the . + /// + /// the unique id of the object that to get. + /// + /// based on the provided. + ConnectorObject GetObject(ObjectClass objClass, Uid uid, OperationOptions options); + } + + /// + /// Get the schema from the . + /// + public interface SchemaApiOp : APIOperation + { + /// + /// Retrieve the basic schema of this . + /// + Schema Schema(); + } + + + public interface SearchApiOp : APIOperation + { + /// + /// Search the resource for all objects that match the object class and + /// filter. + /// + /// + /// reduces the number of entries to only those that match the + /// provided. + /// + /// Reduces the number of entries to only those that match the + /// provided, if any. May be null. + /// + /// class responsible for working with the objects returned from + /// the search. + /// + /// additional options that impact the way this operation is run. + /// May be null. + /// The query result or {@code null}. + /// + /// if there is problem during the processing of the results. + SearchResult Search(ObjectClass objectClass, Filter filter, ResultsHandler handler, OperationOptions options); + + } + + /// + /// Runs a script in the same JVM or .Net Runtime as the Connector. + /// + /// + /// That is, if you are using a local framework, the script will be + /// run in your JVM. If you are connected to a remote framework, the + /// script will be run in the remote JVM or .Net Runtime. + /// + /// This API allows an application to run a script in the context + /// of any connector. (A connector need not implement any particular interface + /// in order to enable this.) The minimum contract to which each connector + /// must adhere is as follows: + /// + /// + /// Script will run in the same classloader/execution environment + /// as the connector, so the script will have access to all the classes + /// to which the connector has access. + /// + /// + /// + /// Script will have access to a "connector" variable + /// that is equivalent to an initialized instance of a connector. + /// Thus, at a minimum the script will be able to access + /// . + /// + /// + /// + /// Script will have access to any + /// + /// passed in by the application. + /// + /// + /// + /// + /// + /// A connector that implements + /// may provide more variables than what is described above. + /// A connector also may perform special processing + /// for specific to that connector. + /// Consult the javadoc of each particular connector to find out what + /// additional capabilities, if any, that connector exposes for use in scripts. + /// + /// + /// NOTE: A caller who wants to execute scripts on a connector + /// should assume that a script must not use any method of the connector + /// beyond the minimum contract described above, + /// unless the connector explicitly documents that method as + /// "for use by connector script". The primary function of a connector + /// is to implement the SPI in the context of the Connector framework. + /// In general, no caller should invoke Connector methods directly + /// --whether by a script or by other means. + /// + /// + public interface ScriptOnConnectorApiOp : APIOperation + { + /// + /// Runs the script. + /// + /// - The script and arguments to run. + /// - Additional options that control how the script is + /// run. The framework does not currently recognize any options + /// but specific connectors might. Consult the documentation + /// for each connector to identify supported options. + /// The result of the script. The return type must be + /// a type that the framework supports for serialization. + /// + Object RunScriptOnConnector(ScriptContext request, + OperationOptions options); + } + /// + /// Runs a script on the target resource that a connector manages. + /// + /// + /// This API operation is supported only for a connector that implements + /// . + /// + /// The contract here at the API level is intentionally very loose. + /// Each connector decides what script languages it supports, + /// what running a script on a target resource actually means, + /// and what script options (if any) that connector supports. + /// Refer to the javadoc of each particular connector for more information. + /// + /// + public interface ScriptOnResourceApiOp : APIOperation + { + /// + /// Runs a script on a specific target resource. + /// + /// The script and arguments to run. + /// Additional options which control how the script is + /// run. Please refer to the connector documentation for supported + /// options. + /// The result of the script. The return type must be + /// a type that the connector framework supports for serialization. + /// See for a list of supported return types. + Object RunScriptOnResource(ScriptContext request, + OperationOptions options); + } + /// + /// Poll for synchronization events--i.e., native changes to target objects. + /// + /// + /// This will be supported by + /// connectors that implement . + /// + /// + public interface SyncApiOp : APIOperation + { + /// + /// Request synchronization events--i.e., native changes to target objects. + /// + /// This method will call the specified + /// once to pass back each + /// matching . Once this method + /// returns, this method will no longer invoke the specified handler. + /// + /// + /// Each {@link SyncDelta#getToken() synchronization event contains a + /// token} that can be used to resume reading events starting from that + /// point in the event stream. In typical usage, a client will save the + /// token from the final synchronization event that was received from one + /// invocation of this {@code sync()} method and then pass that token into + /// that client's next call to this {@code sync()} method. This allows a + /// client to "pick up where he left off" in receiving synchronization + /// events. However, a client can pass the token from any + /// synchronization event into a subsequent invocation of this {@code sync()} + /// method. This will return synchronization events (that represent native + /// changes that occurred) immediately subsequent to the event from which the + /// client obtained the token. + /// + /// + /// A client that wants to read synchronization events "starting now" can + /// call and then pass that token into this + /// {@code sync()} method. + /// + /// + /// + /// + /// The class of object for which to return synchronization + /// events. Must not be null. + /// + /// The token representing the last token from the previous sync. + /// The {@code SyncResultsHandler} will return any number of + /// objects, each of which contains a + /// token. Should be {@code null} if this is the client's first + /// call to the {@code sync()} method for this connector. + /// + /// The result handler. Must not be null. + /// + /// Options that affect the way this operation is run. May be + /// null. + /// The sync token or {@code null}. + /// + /// if {@code objectClass} or {@code handler} is null or if any + /// argument is invalid. + SyncToken Sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler, OperationOptions options); + + + /// + /// Returns the token corresponding to the most recent synchronization event + /// for any instance of the specified object class. + /// + /// An application that wants to receive synchronization events + /// "starting now" --i.e., wants to receive only native changes that occur + /// after this method is called-- should call this method and then pass the + /// resulting token into . + /// + /// + /// + /// + /// This is to support applications that may wish to sync starting + /// "now". + /// + /// + /// the class of object for which to find the most recent + /// synchronization event (if any). + /// A token if synchronization events exist; otherwise {@code null}. + SyncToken GetLatestSyncToken(ObjectClass objectClass); + } + + #region ISyncEventSubscriptionApiOp + /// + /// since 1.5 + public interface ISyncEventSubscriptionApiOp : APIOperation + { + /// + /// Create a subscription to a given sync topic. + /// + /// + /// + /// + /// + /// @return + /// + /// when the operation failed to create subscription. + ISubscription Subscribe(ObjectClass objectClass, SyncToken token, IObserver handler, OperationOptions operationOptions); + } + #endregion + + /// + /// Updates a . + /// + /// + /// This operation + /// is supported for those connectors that implement + /// either or the more advanced + /// . + /// + public interface UpdateApiOp : APIOperation + { + /// + /// Update the object specified by the and , + /// replacing the current values of each attribute with the values + /// provided. + /// + /// + /// + /// For each input attribute, replace + /// all of the current values of that attribute in the target object with + /// the values of that attribute. + /// + /// + /// If the target object does not currently contain an attribute that the + /// input set contains, then add this + /// attribute (along with the provided values) to the target object. + /// + /// + /// If the value of an attribute in the input set is + /// null, then do one of the following, depending on + /// which is most appropriate for the target: + /// + /// + /// If possible, remove that attribute from the target + /// object entirely. + /// + /// + /// + /// Otherwise, replace all of the current values of that + /// attribute in the target object with a single value of + /// null. + /// + /// + /// + /// + /// + /// the type of object to modify. Must not be null. + /// the uid of the object to modify. Must not be null. + /// set of new . the values in this set + /// represent the new, merged values to be applied to the object. + /// This set may also include . + /// Must not be null. + /// additional options that impact the way this operation is run. + /// May be null. + /// the of the updated object in case the update changes + /// the formation of the unique identifier. + /// iff the does not exist on the resource. + Uid Update(ObjectClass objclass, + Uid uid, + ICollection replaceAttributes, + OperationOptions options); + + /// + /// Update the object specified by the and , + /// adding to the current values of each attribute the values provided. + /// + /// + /// + /// For each attribute that the input set contains, add to + /// the current values of that attribute in the target object all of the + /// values of that attribute in the input set. + /// + /// + /// NOTE that this does not specify how to handle duplicate values. + /// The general assumption for an attribute of a ConnectorObject + /// is that the values for an attribute may contain duplicates. + /// Therefore, in general simply append the provided values + /// to the current value for each attribute. + /// + /// + /// IMPLEMENTATION NOTE: for connectors that merely implement + /// and not this method will be simulated by + /// fetching, merging, and calling + /// . Therefore, + /// connector implementations are encourage to implement + /// from a performance and atomicity standpoint. + /// + /// + /// the type of object to modify. Must not be null. + /// the uid of the object to modify. Must not be null. + /// set of deltas. The values for the attributes + /// in this set represent the values to add to attributes in the object. + /// merged. This set must not include . + /// Must not be null. + /// additional options that impact the way this operation is run. + /// May be null. + /// the of the updated object in case the update changes + /// the formation of the unique identifier. + /// iff the does not exist on the resource. + Uid AddAttributeValues(ObjectClass objclass, + Uid uid, + ICollection valuesToAdd, + OperationOptions options); + + /// + /// Update the object specified by the and , + /// removing from the current values of each attribute the values provided. + /// + /// + /// + /// For each attribute that the input set contains, + /// remove from the current values of that attribute in the target object + /// any value that matches one of the values of the attribute from the input set. + /// + /// + /// NOTE that this does not specify how to handle unmatched values. + /// The general assumption for an attribute of a ConnectorObject + /// is that the values for an attribute are merely representational state. + /// Therefore, the implementer should simply ignore any provided value + /// that does not match a current value of that attribute in the target + /// object. Deleting an unmatched value should always succeed. + /// + /// + /// IMPLEMENTATION NOTE: for connectors that merely implement + /// and not this method will be simulated by + /// fetching, merging, and calling + /// . Therefore, + /// connector implementations are encourage to implement + /// from a performance and atomicity standpoint. + /// + /// + /// the type of object to modify. Must not be null. + /// the uid of the object to modify. Must not be null. + /// set of deltas. The values for the attributes + /// in this set represent the values to remove from attributes in the object. + /// merged. This set must not include . + /// Must not be null. + /// additional options that impact the way this operation is run. + /// May be null. + /// the of the updated object in case the update changes + /// the formation of the unique identifier. + /// iff the does not exist on the resource. + Uid RemoveAttributeValues(ObjectClass objclass, + Uid uid, + ICollection valuesToRemove, + OperationOptions options); + + } + + /// + /// Validates the . + /// + /// A valid configuration is one that is ready to be used by the connector: + /// it is complete (all the required properties have been given values) + /// and the property values are well-formed (are in the expected range, + /// have the expected format, etc.) + /// + public interface ValidateApiOp : APIOperation + { + /// + /// Validates the . + /// + /// iff the configuration is not valid. + void Validate(); + } + + /// + /// Tests the with the connector. + /// + /// Unlike validation performed by , testing a configuration should + /// check that any pieces of environment referred by the configuration are available. + /// For example the connector could make a physical connection to a host specified + /// in the configuration to check that it exists and that the credentials + /// specified in the configuration are usable. + /// + /// + /// Since this operation may connect to the resource, it may be slow. Clients are + /// advised not to invoke this operation often, such as before every provisioning operation. + /// This operation is not intended to check that the connector is alive + /// (i.e., its physical connection to the resource has not timed out). + /// + /// + /// This operation may be invoked before the configuration has been validated. + /// + /// + public interface TestApiOp : APIOperation + { + /// + /// Tests the current with the connector. + /// + /// iff the configuration is not valid or the test failed. + void Test(); + } +} \ No newline at end of file diff --git a/dotnet/framework/Framework/Common.cs b/dotnet/framework/Framework/Common.cs new file mode 100644 index 00000000..fc37a0a6 --- /dev/null +++ b/dotnet/framework/Framework/Common.cs @@ -0,0 +1,992 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014-2015 ForgeRock AS. + */ +using System; +using System.Collections; +using System.Reflection; +using System.Text; +using System.Collections.Generic; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Spi.Operations; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; + +namespace Org.IdentityConnectors.Framework.Common +{ + + #region ConnectorKeyRange + + /// + /// A ConnectorKeyRange identifies a range of ConnectorKeys. + /// + /// The uniquely identifies one connector with exact + /// meanwhile this class has a + /// which can match multiple. + /// + /// + /// since 1.5 + public sealed class ConnectorKeyRange + { + private readonly String _bundleName; + private readonly VersionRange _bundleVersionRange; + private readonly String _exactVersion; + private readonly String _connectorName; + + public string BundleName + { + get { return _bundleName; } + } + + public VersionRange BundleVersionRange + { + get { return _bundleVersionRange; } + } + + public string ConnectorName + { + get { return _connectorName; } + } + + public bool IsInRange(ConnectorKey connectorKey) + { + return !_bundleVersionRange.Empty && _bundleName.Equals(connectorKey.BundleName) && + _connectorName.Equals(connectorKey.ConnectorName) && + _bundleVersionRange.IsInRange(Version.Parse(connectorKey.BundleVersion)); + } + + public ConnectorKey ExactConnectorKey + { + get + { + if (_bundleVersionRange.Exact) + { + return new ConnectorKey(_bundleName, _exactVersion, _connectorName); + } + throw new ArgumentException("BundleVersion is not exact version"); + } + } + + private ConnectorKeyRange(String bundleName, String bundleVersion, String connectorName) + { + _bundleName = bundleName; + _exactVersion = bundleVersion; + _bundleVersionRange = VersionRange.Parse(_exactVersion); + _connectorName = connectorName; + } + + public override bool Equals(object o) + { + if (this == o) + { + return true; + } + if (!(o is ConnectorKeyRange)) + { + return false; + } + + ConnectorKeyRange that = (ConnectorKeyRange) o; + + return _bundleName.Equals(that._bundleName) && _bundleVersionRange.Equals(that._bundleVersionRange) && + _connectorName.Equals(that._connectorName); + } + + public override int GetHashCode() + { + int result = _bundleName.GetHashCode(); + result = 31*result + _bundleVersionRange.GetHashCode(); + result = 31*result + _connectorName.GetHashCode(); + return result; + } + + public static Builder NewBuilder() + { + return new Builder(); + } + + public class Builder + { + private string _bundleName; + private string _bundleVersion; + private string _connectorName; + + public virtual Builder SetBundleName(string bundleName) + { + _bundleName = Assertions.BlankChecked(bundleName, "bundleName"); + return this; + } + + public virtual Builder SetBundleVersion(string bundleVersion) + { + _bundleVersion = Assertions.BlankChecked(bundleVersion, "bundleVersion"); + return this; + } + + public virtual Builder SetConnectorName(string connectorName) + { + _connectorName = Assertions.BlankChecked(connectorName, "connectorName"); + return this; + } + + public virtual ConnectorKeyRange Build() + { + return new ConnectorKeyRange(_bundleName, _bundleVersion, _connectorName); + } + } + } + + #endregion + + #region FrameworkInternalBridge + internal static class FrameworkInternalBridge + { + private static readonly Object LOCK = new Object(); + private static Assembly _assembly = null; + /// + /// Loads a class from the FrameworkInternal module + /// + /// + /// + public static SafeType LoadType(String typeName) where T : class + { + + Assembly assembly; + lock (LOCK) + { + if (_assembly == null) + { + AssemblyName assemName = new AssemblyName(); + assemName.Name = "FrameworkInternal"; + _assembly = Assembly.Load(assemName); + } + assembly = _assembly; + } + + return SafeType.ForRawType(assembly.GetType(typeName, true)); + + } + } + #endregion + + #region FrameworkUtil + public static class FrameworkUtil + { + private static readonly IDictionary, SafeType> SPI_TO_API; + private static readonly ICollection CONFIG_SUPPORTED_TYPES; + private static readonly ICollection ATTR_SUPPORTED_TYPES; + + static FrameworkUtil() + { + IDictionary, SafeType> temp = + new Dictionary, SafeType>(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.ForRawType(typeof(SearchOp<>))] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + temp[SafeType.Get()] = + SafeType.Get(); + SPI_TO_API = CollectionUtil.NewReadOnlyDictionary(temp); + + CONFIG_SUPPORTED_TYPES = CollectionUtil.NewReadOnlySet + ( + typeof(string), + typeof(long), + typeof(long?), + typeof(char), + typeof(char?), + typeof(double), + typeof(double?), + typeof(float), + typeof(float?), + typeof(int), + typeof(int?), + typeof(bool), + typeof(bool?), + typeof(Uri), + typeof(FileName), + typeof(GuardedByteArray), + typeof(GuardedString), + typeof(Script) + ); + ATTR_SUPPORTED_TYPES = CollectionUtil.NewReadOnlySet + ( + typeof(string), + typeof(long), + typeof(long?), + typeof(char), + typeof(char?), + typeof(double), + typeof(double?), + typeof(float), + typeof(float?), + typeof(int), + typeof(int?), + typeof(bool), + typeof(bool?), + typeof(byte), + typeof(byte?), + typeof(byte[]), + typeof(BigDecimal), + typeof(BigInteger), + typeof(GuardedByteArray), + typeof(GuardedString), + typeof(IDictionary) + ); + + } + + + /// + /// Determines if the class is a supported attribute type. + /// + /// + /// If not it throws + /// an . + /// + /// + /// string + /// + /// + /// + /// long + /// + /// + /// + /// long? + /// + /// + /// + /// char + /// + /// + /// + /// char? + /// + /// + /// + /// double + /// + /// + /// + /// double? + /// + /// + /// + /// float + /// + /// + /// + /// float? + /// + /// + /// + /// int + /// + /// + /// + /// int? + /// + /// + /// + /// bool + /// + /// + /// + /// bool? + /// + /// + /// + /// byte + /// + /// + /// + /// byte? + /// + /// + /// + /// byte[] + /// + /// + /// + /// BigDecimal + /// + /// + /// + /// BigInteger + /// + /// + /// + /// IDictionary + /// + /// + /// + /// + /// type to check against the support list of types. + /// iff the type is not on the supported list. + public static void CheckAttributeType(Type type) + { + if (!FrameworkUtil.IsSupportedAttributeType(type)) + { + String MSG = "Attribute type ''" + type + "'' is not supported."; + throw new ArgumentException(MSG); + } + } + /// + /// Determines if the class of the object is a supported attribute type. If + /// not it throws an . + /// + /// + /// The value to check or null. + /// + /// If the class of the object is a supported attribute type. + public static void CheckAttributeValue(Object value) + { + if (value != null) + { + CheckAttributeValue((String)null, value); + } + } + /// + /// Determines if the class of the object is a supported attribute type. If + /// not it throws an . + /// + /// + /// The name of the attribute to check + /// + /// The value to check or null. + /// + /// If the class of the object is a supported attribute type. + public static void CheckAttributeValue(String name, Object value) + { + + if (value != null) + { + if (value is IDictionary) + { + CheckAttributeValue(new StringBuilder(name == null ? "?" : name), value); + } + else + { + if (name == null) + { + CheckAttributeType(value.GetType()); + } + else if (!IsSupportedAttributeType(value.GetType())) + { + throw new ArgumentException("Attribute ''" + name + "'' type ''" + value.GetType() + "'' is not supported."); + } + } + } + } + private static void CheckAttributeValue(StringBuilder name, Object value) + { + if (value != null) + { + IDictionary dictionary = value as IDictionary; + if (dictionary != null) + { + foreach (DictionaryEntry entry in dictionary) + { + object key = entry.Key; + Object entryValue = entry.Value; + if (key is string) + { + StringBuilder nameBuilder = (new StringBuilder(name.ToString())).Append('/').Append(key); + if (entryValue is IList) + { + nameBuilder.Append("[*]"); + foreach (Object item in ((IList)entryValue)) + { + CheckAttributeValue(nameBuilder, item); + } + } + else + { + CheckAttributeValue(nameBuilder, entryValue); + } + } + else + { + throw new ArgumentException( + "Map Attribute ''" + name + "'' must have String key, type ''" + key.GetType() + "'' is not supported."); + } + } + } + else + { + if (!IsSupportedAttributeType(value.GetType())) + { + throw new ArgumentException("Attribute ''" + name + "'' type ''" + value.GetType() + "'' is not supported."); + } + } + } + } + public static ICollection> Spi2Apis(SafeType type) + { + type = type.GetTypeErasure(); + HashSet> set = new HashSet>(); + set.Add(SPI_TO_API[type]); + // add GetApiOp if search is available.. + + if (type.RawType.Equals(typeof(SearchOp<>))) + { + set.Add(SafeType.Get()); + } + return set; + } + public static ICollection> AllSPIOperations() + { + return SPI_TO_API.Keys; + } + public static ICollection> AllAPIOperations() + { + ICollection> set = + new HashSet>(); + CollectionUtil.AddAll(set, + SPI_TO_API.Values); + // add Get because it doesn't have a corresponding SPI. + set.Add(SafeType.Get()); + set.Add(SafeType.Get()); + return CollectionUtil.AsReadOnlySet(set); + } + public static ICollection> GetDefaultSupportedOperations(SafeType connector) + { + ICollection> rv = + new HashSet>(); + ICollection interfaces = + ReflectionUtil.GetTypeErasure(ReflectionUtil.GetAllInterfaces(connector.RawType)); + foreach (SafeType spi in AllSPIOperations()) + { + if (interfaces.Contains(spi.RawType)) + { + CollectionUtil.AddAll(rv, Spi2Apis(spi)); + } + } + //finally add unconditionally supported ops + CollectionUtil.AddAll(rv, GetUnconditionallySupportedOperations()); + return CollectionUtil.AsReadOnlySet(rv); + } + public static ICollection> GetUnconditionallySupportedOperations() + { + HashSet> ret; + ret = new HashSet>(); + //add validate api op always + ret.Add(SafeType.Get()); + //add ScriptOnConnectorApiOp always + ret.Add(SafeType.Get()); + return ret; + } + public static ICollection GetAllSupportedConfigTypes() + { + return CONFIG_SUPPORTED_TYPES; + } + public static bool IsSupportedConfigurationType(Type type) + { + if (type.IsArray) + { + return IsSupportedConfigurationType(type.GetElementType()); + } + else + { + return CONFIG_SUPPORTED_TYPES.Contains(type); + } + } + public static ICollection GetAllSupportedAttributeTypes() + { + return ATTR_SUPPORTED_TYPES; + } + public static bool IsSupportedAttributeType(Type clazz) + { + return ATTR_SUPPORTED_TYPES.Contains(clazz) || null != ReflectionUtil.FindInHierarchyOf + (typeof(IDictionary<,>), clazz); + } + + /// + /// Determines if the class is a supported type for an OperationOption. + /// + /// + /// If not it throws + /// an . + /// + /// type to check against the support list of types. + /// iff the type is not on the supported list. + public static void CheckOperationOptionType(Type clazz) + { + //the set of supported operation option types + //is the same as that for configuration beans plus Name, + //ObjectClass, Uid, and QualifiedUid + + if (clazz.IsArray) + { + CheckOperationOptionType(clazz.GetElementType()); + return; + } + + if (FrameworkUtil.IsSupportedConfigurationType(clazz)) + { + return; //ok + } + + if (typeof(ObjectClass).IsAssignableFrom(clazz)) + { + return; //ok + } + + if (typeof(Uid).IsAssignableFrom(clazz)) + { + return; //ok + } + + if (typeof(QualifiedUid).IsAssignableFrom(clazz)) + { + return; //ok + } + + if (typeof(SortKey).IsAssignableFrom(clazz)) + { + return; //ok + } + + String MSG = "ConfigurationOption type '+" + clazz.Name + "+' is not supported."; + throw new ArgumentException(MSG); + } + /// + /// Determines if the class of the object is a supported attribute type. + /// + /// + /// If not it throws an . + /// + /// The value to check or null. + public static void CheckOperationOptionValue(Object val) + { + if (val != null) + { + CheckOperationOptionType(val.GetType()); + } + } + + /// + /// Check if + /// was invoked from + /// or + /// . + /// + /// + /// the query parameter of + /// + /// {@code null} if invoked from + /// + /// or value if invoked + /// + /// + /// since 1.5 + public static Uid GetUidIfGetOperation(Filter filter) + { + Uid uidToGet = null; + var equalsFilter = filter as EqualsFilter; + if (equalsFilter != null) + { + if (equalsFilter.GetAttribute() is Uid) + { + uidToGet = (Uid)equalsFilter.GetAttribute(); + } + else if (equalsFilter.GetAttribute().@Is(Uid.NAME)) + { + uidToGet = new Uid(ConnectorAttributeUtil.GetStringValue(equalsFilter.GetAttribute())); + } + } + return uidToGet; + } + + /// + /// Returns the version of the framework. + /// + /// the framework version; never null. + public static Version GetFrameworkVersion() + { + return Assembly.GetExecutingAssembly().GetName().Version; + } + } + #endregion + + #region VersionRange + /// + /// A version range is an interval describing a set of . + ///

+ /// A range has a left (lower) endpoint and a right (upper) endpoint. Each + /// endpoint can be open (excluded from the set) or closed (included in the set). + /// + ///

+ /// {@code VersionRange} objects are immutable. + /// + /// @author Laszlo Hordos + /// @Immutable + ///

+ public class VersionRange + { + + /// + /// The left endpoint is open and is excluded from the range. + ///

+ /// The value of {@code LEFT_OPEN} is {@code '('}. + ///

+ public const char LEFT_OPEN = '('; + /// + /// The left endpoint is closed and is included in the range. + ///

+ /// The value of {@code LEFT_CLOSED} is {@code '['}. + ///

+ public const char LEFT_CLOSED = '['; + /// + /// The right endpoint is open and is excluded from the range. + ///

+ /// The value of {@code RIGHT_OPEN} is {@code ')'}. + ///

+ public const char RIGHT_OPEN = ')'; + /// + /// The right endpoint is closed and is included in the range. + ///

+ /// The value of {@code RIGHT_CLOSED} is {@code ']'}. + ///

+ public const char RIGHT_CLOSED = ']'; + + private const string ENDPOINT_DELIMITER = ","; + + private readonly Version floorVersion; + private readonly bool isFloorInclusive; + private readonly Version ceilingVersion; + private readonly bool isCeilingInclusive; + private readonly bool empty; + + /// + /// Parse version component into a Version. + /// + /// + /// version component string + /// + /// Complete range string for exception message, if any + /// Version + private static Version parseVersion(string version, string range) + { + try + { + return Version.Parse(version); + } + catch (System.ArgumentException e) + { + throw new System.ArgumentException("invalid range \"" + range + "\": " + e.Message, e); + } + } + + /// + /// Creates a version range from the specified string. + /// + ///

+ /// Version range string grammar: + /// + ///

+        /// range ::= interval | at least
+        /// interval ::= ( '[' | '(' ) left ',' right ( ']' | ')' )
+        /// left ::= version
+        /// right ::= version
+        /// at least ::= version
+        /// 
+ ///
+ /// + /// String representation of the version range. The versions in + /// the range must contain no whitespace. Other whitespace in the + /// range string is ignored. + /// + /// If {@code range} is improperly formatted. + public static VersionRange Parse(string range) + { + Assertions.BlankCheck(range, "range"); + int idx = range.IndexOf(ENDPOINT_DELIMITER); + // Check if the version is an interval. + if (idx > 1 && idx == range.LastIndexOf(ENDPOINT_DELIMITER)) + { + string vlo = range.Substring(0, idx).Trim(); + string vhi = range.Substring(idx + 1).Trim(); + + bool isLowInclusive = true; + bool isHighInclusive = true; + if (vlo[0] == LEFT_OPEN) + { + isLowInclusive = false; + } + else if (vlo[0] != LEFT_CLOSED) + { + throw new System.ArgumentException("invalid range \"" + range + "\": invalid format"); + } + vlo = vlo.Substring(1).Trim(); + + if (vhi[vhi.Length - 1] == RIGHT_OPEN) + { + isHighInclusive = false; + } + else if (vhi[vhi.Length - 1] != RIGHT_CLOSED) + { + throw new System.ArgumentException("invalid range \"" + range + "\": invalid format"); + } + vhi = vhi.Substring(0, vhi.Length - 1).Trim(); + + return new VersionRange(parseVersion(vlo, range), isLowInclusive, parseVersion(vhi, range), isHighInclusive); + } + else if (idx == -1) + { + return new VersionRange(VersionRange.parseVersion(range.Trim(), range), true, null, false); + } + else + { + throw new System.ArgumentException("invalid range \"" + range + "\": invalid format"); + } + } + + public VersionRange(Version low, bool isLowInclusive, Version high, bool isHighInclusive) + { + Assertions.NullCheck(low, "floorVersion"); + floorVersion = low; + isFloorInclusive = isLowInclusive; + ceilingVersion = high; + isCeilingInclusive = isHighInclusive; + empty = Empty0; + } + + public Version Floor + { + get + { + return floorVersion; + } + } + + public bool FloorInclusive + { + get + { + return isFloorInclusive; + } + } + + public Version Ceiling + { + get + { + return ceilingVersion; + } + } + + public bool CeilingInclusive + { + get + { + return isCeilingInclusive; + } + } + + public bool IsInRange(Version version) + { + if (empty) + { + return false; + } + int c = floorVersion.CompareTo(version); + if (c == 0 && isFloorInclusive) + { + return true; + } + if (c < 0 && ceilingVersion != null) + { + return ceilingVersion.CompareTo(version) >= (isCeilingInclusive ? 0 : 1); + } + return false; + } + + /// + /// Returns whether this version range contains only a single version. + /// + /// {@code true} if this version range contains only a single + /// version; {@code false} otherwise. + public bool Exact + { + get + { + if (empty) + { + return false; + } + else if (ceilingVersion == null) + { + return true; + } + if (isFloorInclusive) + { + if (isCeilingInclusive) + { + // [f,c]: exact if f == c + return floorVersion.Equals(ceilingVersion); + } + else + { + // [f,c): exact if f++ >= c + Version adjacent1 = new Version(floorVersion.Major, floorVersion.Minor, floorVersion.Build, floorVersion.Revision + 1); + return adjacent1.CompareTo(ceilingVersion) >= 0; + } + } + else + { + if (isCeilingInclusive) + { + // (f,c] is equivalent to [f++,c]: exact if f++ == c + Version adjacent1 = new Version(floorVersion.Major, floorVersion.Minor, floorVersion.Build, floorVersion.Revision + 1); + return adjacent1.Equals(ceilingVersion); + } + else + { + // (f,c) is equivalent to [f++,c): exact if (f++)++ >=c + Version adjacent2 = new Version(floorVersion.Major, floorVersion.Minor, floorVersion.Build, floorVersion.Revision + 2); + return adjacent2.CompareTo(ceilingVersion) >= 0; + } + } + } + } + + /// + /// Returns whether this version range is empty. A version range is empty if + /// the set of versions defined by the interval is empty. + /// + /// {@code true} if this version range is empty; {@code false} + /// otherwise. + public bool Empty + { + get + { + return empty; + } + } + + /// + /// Internal isEmpty behavior. + /// + /// {@code true} if this version range is empty; {@code false} + /// otherwise. + private bool Empty0 + { + get + { + if (ceilingVersion == null) // infinity + { + return false; + } + int comparison = floorVersion.CompareTo(ceilingVersion); + if (comparison == 0) // endpoints equal + { + return !isFloorInclusive || !isCeilingInclusive; + } + return comparison > 0; // true if left > right + } + } + + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + if (this.GetType() != obj.GetType()) + { + return false; + } + VersionRange other = (VersionRange)obj; + if (floorVersion != other.floorVersion && (floorVersion == null || !floorVersion.Equals(other.floorVersion))) + { + return false; + } + if (isFloorInclusive != other.isFloorInclusive) + { + return false; + } + if (ceilingVersion != other.ceilingVersion && (ceilingVersion == null || !ceilingVersion.Equals(other.ceilingVersion))) + { + return false; + } + if (isCeilingInclusive != other.isCeilingInclusive) + { + return false; + } + return true; + } + + public override int GetHashCode() + { + int result = floorVersion.GetHashCode(); + result = 31 * result + (isFloorInclusive ? 1 : 0); + result = 31 * result + (ceilingVersion != null ? ceilingVersion.GetHashCode() : 0); + result = 31 * result + (isCeilingInclusive ? 1 : 0); + return result; + } + + public override string ToString() + { + if (ceilingVersion != null) + { + StringBuilder sb = new StringBuilder(); + sb.Append(isFloorInclusive ? LEFT_CLOSED : LEFT_OPEN); + sb.Append(floorVersion.ToString()).Append(ENDPOINT_DELIMITER).Append(ceilingVersion.ToString()); + sb.Append(isCeilingInclusive ? RIGHT_CLOSED : RIGHT_OPEN); + return sb.ToString(); + } + else + { + return floorVersion.ToString(); + } + } + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/Framework/CommonExceptions.cs b/dotnet/framework/Framework/CommonExceptions.cs new file mode 100644 index 00000000..f7432952 --- /dev/null +++ b/dotnet/framework/Framework/CommonExceptions.cs @@ -0,0 +1,841 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2014 ForgeRock AS. + */ +using System; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Common; + +namespace Org.IdentityConnectors.Framework.Common.Exceptions +{ + #region AlreadyExistsException + /// + /// AlreadyExistsException is thrown to indicate if + /// attempts + /// to create an object that exists prior to the method execution or + /// attempts + /// to rename an object to that exists prior to the method execution. + /// + public class AlreadyExistsException : ConnectorException + { + [NonSerialized] + private Uid _uid; + + /// + public AlreadyExistsException() + : base() + { + } + + /// + public AlreadyExistsException(String message) + : base(message) + { + + } + + /// + public AlreadyExistsException(Exception ex) + : base(ex) + { + } + + /// + public AlreadyExistsException(String message, Exception ex) + : base(message, ex) + { + } + + public Uid Uid + { + get + { + return _uid; + } + } + + /// + /// Sets the Uid of existing Object. + /// + /// Connectors who throw this exception from their + /// or + /// should + /// set the object's Uid if available. + /// + /// + /// The uid. + /// A reference to this. + public AlreadyExistsException InitUid(Uid uid) + { + this._uid = uid; + return this; + } + } + #endregion + + #region ConfigurationException + public class ConfigurationException : ConnectorException + { + public ConfigurationException() + : base() + { + } + + public ConfigurationException(String message) + : base(message) + { + } + + public ConfigurationException(Exception ex) + : base(ex) + { + } + + public ConfigurationException(String message, Exception ex) + : base(message, ex) + { + } + } + #endregion + + #region ConnectionBrokenException + /// + /// ConnectionBrokenException is thrown when a connection to a target resource + /// instance fails during an operation. + /// + /// An instance of ConnectionBrokenException generally wraps the + /// native exception (or describes the native error) returned by the target + /// resource. + /// + public class ConnectionBrokenException : ConnectorIOException + { + + /// + public ConnectionBrokenException() + : base() + { + } + + /// + public ConnectionBrokenException(String msg) + : base(msg) + { + } + + /// + public ConnectionBrokenException(Exception ex) + : base(ex) + { + } + + /// + public ConnectionBrokenException(String message, Exception ex) + : base(message, ex) + { + } + } + #endregion + + #region ConnectionFailedException + /// + /// ConnectionFailedException is thrown when a Connector cannot reach the target + /// resource instance. + /// + /// An instance of ConnectionFailedException generally wraps the + /// native exception (or describes the native error) returned by the target + /// resource. + /// + public class ConnectionFailedException : ConnectorIOException + { + + /// + public ConnectionFailedException() + : base() + { + } + + /// + public ConnectionFailedException(String msg) + : base(msg) + { + } + + /// + public ConnectionFailedException(Exception ex) + : base(ex) + { + } + + /// + public ConnectionFailedException(String message, Exception ex) + : base(message, ex) + { + } + } + #endregion + + #region ConnectorException + public class ConnectorException : ApplicationException + { + public ConnectorException() + : base() + { + } + + /// + /// Sets a message for the . + /// + /// passed to the message. + public ConnectorException(String message) + : base(message) + { + } + + /// + /// Sets the stack trace to the original exception, so this exception can + /// masquerade as the original only be a . + /// + /// the original exception adapted to . + public ConnectorException(Exception ex) + : base(ex.Message, ex) + { + } + + /// + /// Sets the stack trace to the original exception, so this exception can + /// masquerade as the original only be a . + /// + /// + /// the original exception adapted to . + public ConnectorException(String message, Exception originalException) + : base(message, originalException) + { + } + + } + #endregion + + #region ConnectorIOException + public class ConnectorIOException : ConnectorException + { + public ConnectorIOException() + : base() + { + } + + public ConnectorIOException(String msg) + : base(msg) + { + } + + public ConnectorIOException(Exception ex) + : base(ex) + { + } + + public ConnectorIOException(String message, Exception ex) + : base(message, ex) + { + } + } + #endregion + + #region ConnectorSecurityException + public class ConnectorSecurityException : ConnectorException + { + public ConnectorSecurityException() + : base() + { + } + + public ConnectorSecurityException(String message) + : base(message) + { + } + + public ConnectorSecurityException(Exception ex) + : base(ex) + { + } + + public ConnectorSecurityException(String message, Exception ex) + : base(message, ex) + { + } + } + #endregion + + #region InvalidAttributeValueException + /// + /// InvalidAttributeValueException is thrown when an attempt is made to add to an + /// attribute a value that conflicts with the attribute's schema definition. + /// + /// This could happen, for example, if attempting to add an attribute with no + /// value when the attribute is required to have at least one value, or if + /// attempting to add more than one value to a single valued-attribute, or if + /// attempting to add a value that conflicts with the type of the attribute or if + /// attempting to add a value that conflicts with the syntax of the attribute. + /// + /// Since 1.4 + public class InvalidAttributeValueException : ConnectorException + { + /// + /// Constructs a new InvalidAttributeValueException exception with + /// null as its detail message. The cause is not initialized, + /// and may subsequently be initialized by a call to . + /// + public InvalidAttributeValueException() + : base() + { + } + + /// + /// Constructs a new InvalidAttributeValueException exception with the specified + /// detail message. The cause is not initialized, and may subsequently be + /// initialized by a call to . + /// + /// + /// the detail message. The detail message is is a String that + /// describes this particular exception and saved for later + /// retrieval by the method. + public InvalidAttributeValueException(string message) + : base(message) + { + } + + /// + /// Constructs a new InvalidAttributeValueException exception with the specified + /// cause and a detail message of + /// (cause==null ? null : cause.toString()) (which typically + /// contains the class and detail message of cause). This + /// constructor is useful for InvalidAccountException exceptions that are + /// little more than wrappers for other throwables. + /// + /// + /// the cause (which is saved for later retrieval by the + /// method). (A null value is + /// permitted, and indicates that the cause is nonexistent or + /// unknown.) + public InvalidAttributeValueException(Exception cause) + : base(cause) + { + } + + /// + /// Constructs a new InvalidAttributeValueException exception with the specified + /// detail message and cause. + /// + /// Note that the detail message associated with cause is + /// not automatically incorporated in this Connector exception's + /// detail message. + /// + /// + /// + /// + /// the detail message (which is saved for later retrieval by the + /// method). + /// + /// the cause (which is saved for later retrieval by the + /// method). (A null value is + /// permitted, and indicates that the cause is nonexistent or + /// unknown.) + public InvalidAttributeValueException(string message, Exception cause) + : base(message, cause) + { + } + } + #endregion + + #region InvalidCredentialException + /// + /// InvalidCredentialException signals that user authentication failed. + ///

+ /// This exception is thrown by Connector if authentication failed. For example, + /// a Connector throws this exception if the user entered an + /// incorrect password. + ///

+ public class InvalidCredentialException : ConnectorSecurityException + { + + /// + public InvalidCredentialException() + : base() + { + } + + /// + public InvalidCredentialException(String message) + : base(message) + { + } + + /// + public InvalidCredentialException(Exception ex) + : base(ex) + { + } + + /// + public InvalidCredentialException(String message, Exception ex) + : base(message, ex) + { + } + } + #endregion + + #region InvalidPasswordException + public class InvalidPasswordException : InvalidCredentialException + { + public InvalidPasswordException() + : base() + { + } + + public InvalidPasswordException(String message) + : base(message) + { + } + + public InvalidPasswordException(Exception ex) + : base(ex) + { + } + + public InvalidPasswordException(String message, Exception ex) + : base(message, ex) + { + } + } + #endregion + + #region OperationTimeoutException + public class OperationTimeoutException : ConnectorException + { + public OperationTimeoutException() + : base() + { + } + + public OperationTimeoutException(String msg) + : base(msg) + { + } + + public OperationTimeoutException(Exception e) + : base(e) + { + } + + public OperationTimeoutException(String msg, Exception e) + : base(msg, e) + { + } + + } + #endregion + + #region PasswordExpiredException + /// + /// PasswordExpiredException signals that a user password has expired. + /// + /// This exception is thrown by Connector when they determine that a password has + /// expired. For example, a Connector, after successfully + /// authenticating a user, may determine that the user's password has expired. In + /// this case the Connector throws this exception to notify the + /// application. The application can then take the appropriate steps to notify + /// the user. + /// + /// + /// + public class PasswordExpiredException : InvalidPasswordException + { + [NonSerialized] + private Uid _uid; + + /// + public PasswordExpiredException() + : base() + { + } + + /// + public PasswordExpiredException(String message) + : base(message) + { + } + + /// + public PasswordExpiredException(Exception ex) + : base(ex) + { + } + + /// + public PasswordExpiredException(String message, Exception ex) + : base(message, ex) + { + } + + public Uid Uid + { + get + { + return _uid; + } + /// + /// Sets the Uid. Connectors who throw this exception from their + /// should set the account Uid if available. + /// + /// + /// The uid. + /// A reference to this. + set + { + _uid = value; + } + } + } + #endregion + + #region PermissionDeniedException + public class PermissionDeniedException : ConnectorSecurityException + { + public PermissionDeniedException() + : base() + { + } + + public PermissionDeniedException(String message) + : base(message) + { + } + + public PermissionDeniedException(Exception ex) + : base(ex) + { + } + + public PermissionDeniedException(String message, Exception ex) + : base(message, ex) + { + } + } + #endregion + + #region PreconditionFailedException + + + /// + /// PreconditionFailedException is thrown to indicate that a resource's current + /// version does not match the version provided. + /// + /// Equivalent to HTTP status: 412 Precondition Failed. + /// + /// Since 1.4 + public class PreconditionFailedException : ConnectorException + { + /// + /// Constructs a new PreconditionFailedException exception with + /// null as its detail message. The cause is not initialized, + /// and may subsequently be initialized by a call to . + /// + public PreconditionFailedException() + : base() + { + } + + /// + /// Constructs a new PreconditionFailedException exception with the specified + /// detail message. The cause is not initialized, and may subsequently be + /// initialized by a call to . + /// + /// + /// the detail message. The detail message is is a String that + /// describes this particular exception and saved for later + /// retrieval by the method. + public PreconditionFailedException(string message) + : base(message) + { + } + + /// + /// Constructs a new PreconditionFailedException exception with the specified + /// cause and a detail message of + /// (cause==null ? null : cause.toString()) (which typically + /// contains the class and detail message of cause). This + /// constructor is useful for InvalidAccountException exceptions that are + /// little more than wrappers for other throwables. + /// + /// + /// the cause (which is saved for later retrieval by the + /// method). (A null value is + /// permitted, and indicates that the cause is nonexistent or + /// unknown.) + public PreconditionFailedException(Exception cause) + : base(cause) + { + } + + /// + /// Constructs a new PreconditionFailedException exception with the specified + /// detail message and cause. + /// + /// Note that the detail message associated with cause is + /// not automatically incorporated in this Connector exception's + /// detail message. + /// + /// + /// + /// + /// the detail message (which is saved for later retrieval by the + /// method). + /// + /// the cause (which is saved for later retrieval by the + /// method). (A null value is + /// permitted, and indicates that the cause is nonexistent or + /// unknown.) + public PreconditionFailedException(string message, Exception cause) + : base(message, cause) + { + } + } + #endregion + + #region PreconditionRequiredException + /// + /// PreconditionRequiredException is thrown to indicate that a resource requires + /// a version, but no version was supplied in the request. + /// + /// Equivalent to draft-nottingham-http-new-status-03 HTTP status: 428 + /// Precondition Required. + /// + /// Since 1.4 + public class PreconditionRequiredException : ConnectorException + { + /// + /// Constructs a new PreconditionRequiredException exception with + /// null as its detail message. The cause is not initialized, + /// and may subsequently be initialized by a call to . + /// + public PreconditionRequiredException() + : base() + { + } + + /// + /// Constructs a new PreconditionRequiredException exception with the + /// specified detail message. The cause is not initialized, and may + /// subsequently be initialized by a call to . + /// + /// + /// the detail message. The detail message is is a String that + /// describes this particular exception and saved for later + /// retrieval by the method. + public PreconditionRequiredException(string message) + : base(message) + { + } + + /// + /// Constructs a new PreconditionRequiredException exception with the + /// specified cause and a detail message of + /// (cause==null ? null : cause.toString()) (which typically + /// contains the class and detail message of cause). This + /// constructor is useful for InvalidAccountException exceptions that are + /// little more than wrappers for other throwables. + /// + /// + /// the cause (which is saved for later retrieval by the + /// method). (A null value is + /// permitted, and indicates that the cause is nonexistent or + /// unknown.) + public PreconditionRequiredException(Exception cause) + : base(cause) + { + } + + /// + /// Constructs a new PreconditionRequiredException exception with the + /// specified detail message and cause. + /// + /// Note that the detail message associated with cause is + /// not automatically incorporated in this Connector exception's + /// detail message. + /// + /// + /// + /// + /// the detail message (which is saved for later retrieval by the + /// method). + /// + /// the cause (which is saved for later retrieval by the + /// method). (A null value is + /// permitted, and indicates that the cause is nonexistent or + /// unknown.) + public PreconditionRequiredException(string message, Exception cause) + : base(message, cause) + { + } + } + #endregion + + #region RetryableException + /// + /// RetryableException indicates that a failure may be temporary, and that + /// retrying the same request may be able to succeed in the future. + /// + /// Since 1.4 + public class RetryableException : ConnectorException + { + /// + /// Constructs a new RetryableException exception with the specified cause + /// and a detail message of (cause==null ? null : cause.toString()) + /// (which typically contains the class and detail message of cause + /// ). This constructor is useful for InvalidAccountException exceptions that + /// are little more than wrappers for other throwables. + /// + /// + /// the cause (which is saved for later retrieval by the + /// method). (A null value is + /// permitted, and indicates that the cause is nonexistent or + /// unknown.) + private RetryableException(Exception cause) + : base(cause) + { + } + + /// + /// Constructs a new RetryableException exception with the specified detail + /// message and cause. + /// + /// Note that the detail message associated with cause is + /// not automatically incorporated in this Connector exception's + /// detail message. + /// + /// + /// + /// + /// the detail message (which is saved for later retrieval by the + /// method). + /// + /// the cause (which is saved for later retrieval by the + /// method). (A null value is + /// permitted, and indicates that the cause is nonexistent or + /// unknown.) + private RetryableException(string message, Exception cause) + : base(message, cause) + { + } + + /// + /// If parameter passed in is a + /// it is simply returned. Otherwise the is wrapped in a + /// RetryableException and returned. + /// + /// + /// the detail message (which is saved for later retrieval by the + /// method). + /// + /// Exception to wrap or cast and return. + /// a RuntimeException that either is the + /// specified exception or contains the specified exception. + public static RetryableException Wrap(string message, Exception cause) + { + // don't bother to wrap a exception that is already a + // RetryableException. + if (cause is RetryableException) + { + return (RetryableException)cause; + } + + if (null != message) + { + return new RetryableException(message, cause); + } + else + { + return new RetryableException(cause); + } + } + + /// + /// Constructs a new RetryableException which signals partial success of + /// create operation. + /// + /// This should be called inside + /// + /// implementation to signal that the create was not completed but the object + /// was created with Uid and Application should call the + /// + /// method now. + ///

+ /// Use this only if the created object can not be deleted. The best-practice + /// should always be the Connector implementation reverts the changes if the + /// operation failed. + ///

+ /// + /// the detail message (which is saved for later retrieval by the + /// method). + /// + /// + /// the new object's Uid. + /// a RetryableException that either is the + /// specified exception or contains the specified exception. + public static RetryableException Wrap(string message, Uid uid) + { + return new RetryableException(message, new AlreadyExistsException().InitUid(Assertions.NullChecked(uid, "Uid"))); + } + } + #endregion + + #region UnknownUidException + public class UnknownUidException : InvalidCredentialException + { + const string MSG = "Object with Uid '{0}' and ObjectClass '{1}' does not exist!"; + + public UnknownUidException() + : base() + { + } + + public UnknownUidException(Uid uid, ObjectClass objclass) : + base(String.Format(MSG, uid, objclass)) + { + } + + public UnknownUidException(String message) + : base(message) + { + } + + public UnknownUidException(Exception ex) + : base(ex) + { + } + + public UnknownUidException(String message, Exception ex) + : base(message, ex) + { + } + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/Framework/CommonObjects.cs b/dotnet/framework/Framework/CommonObjects.cs new file mode 100755 index 00000000..30c3787d --- /dev/null +++ b/dotnet/framework/Framework/CommonObjects.cs @@ -0,0 +1,6256 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014-2015 ForgeRock AS. + */ +using System; +using System.Linq; +using System.Security; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Text; +using System.Threading; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; + +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Serializer; + +namespace Org.IdentityConnectors.Framework.Common.Objects +{ + #region NameUtil + internal static class NameUtil + { + public static bool IsSpecialName(String name) + { + return (name.StartsWith("__") && name.EndsWith("__")); + } + + public static string CreateSpecialName(string name) + { + if (StringUtil.IsBlank(name)) + { + const string ERR = "Name parameter must not be blank!"; + throw new ArgumentException(ERR); + } + return "__" + name + "__"; + } + + public static bool NamesEqual(string name1, string name2) + { + return name1.ToUpper(CultureInfoCache.Instance).Equals( + name2.ToUpper(CultureInfoCache.Instance)); + } + + public static int GetNameHashCode(string name) + { + return name.ToUpper(CultureInfoCache.Instance).GetHashCode(); + } + } + #endregion + + #region ConnectorAttributeUtil + public static class ConnectorAttributeUtil + { + /// + /// Gets the string value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the string value from. + /// null if the value is null otherwise the string value for the + /// attribute. + /// if the object in the attribute is not an string. + /// if the attribute is a multi valued instead of single valued. + public static string GetStringValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (string)obj : null; + } + + /// + /// Gets the character value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the character value from. + /// null if the value is null otherwise the character value for the + /// attribute. + /// if the object in the attribute is not an string. + /// if the attribute is a multi valued instead of single valued. + /// Since 1.4 + public static char? GetCharacterValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (char?)obj : null; + } + + /// + /// Gets the string value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the string value from. + /// null if the value is null otherwise the string value for the + /// attribute. + /// if the attribute is a multi valued instead of single valued. + public static string GetAsStringValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? obj.ToString() : null; + } + + /// + /// Gets the guarded string value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the guarded string value from. + /// null if the value is null otherwise the guarded string value for the + /// attribute. + /// if the object in the attribute is not a guarded string. + /// if the attribute is a multi valued instead of single valued. + public static GuardedString GetGuardedStringValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (GuardedString)obj : null; + } + + /// + /// Gets the guarded byte array value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the guarded byte array value from. + /// null if the value is null otherwise the guarded byte array value for the + /// attribute. + /// if the object in the attribute is not a guarded byte array. + /// if the attribute is a multi valued instead of single valued. + /// Since 1.4 + public static GuardedByteArray GetGuardedByteArrayValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (GuardedByteArray)obj : null; + } + + /// + /// Gets the integer value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the integer value from. + /// null if the value is null otherwise the integer value for the + /// attribute. + /// if the object in the attribute is not an integer. + /// if the attribute is a multi valued instead of single valued. + public static int? GetIntegerValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (int?)obj : null; + } + + /// + /// Gets the long value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the long value from. + /// null if the value is null otherwise the long value for the + /// attribute. + /// if the object in the attribute is not an long. + /// if the attribute is a multi valued instead of single valued. + public static long? GetLongValue(ConnectorAttribute attr) + { + Object obj = GetSingleValue(attr); + return obj != null ? (long?)obj : null; + } + + /// + /// Gets the date value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the date value from. + /// null if the value is null otherwise the date value for the + /// attribute. + /// if the object in the attribute is not an long. + /// if the attribute is a multi valued instead of single valued. + public static DateTime? GetDateTimeValue(ConnectorAttribute attr) + { + long? val = GetLongValue(attr); + if (val != null) + { + return DateTimeUtil.GetDateTimeFromUtcMillis(val.Value); + } + return null; + } + + /// + /// Gets the double value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the double value from. + /// null if the value is null otherwise the double value for the + /// attribute. + /// if the object in the attribute is not a double. + /// if the attribute is a multi valued instead of single valued. + public static double? GetDoubleValue(ConnectorAttribute attr) + { + Object obj = GetSingleValue(attr); + return obj != null ? (double?)obj : null; + } + + /// + /// Gets the float value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the float value from. + /// null if the value is null otherwise the float value for the + /// attribute. + /// if the object in the attribute is not a float. + /// if the attribute is a multi valued instead of single valued. + /// Since 1.4 + public static float? GetFloatValue(ConnectorAttribute attr) + { + Object obj = GetSingleValue(attr); + return obj != null ? (float?)obj : null; + } + + /// + /// Gets the byte value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the byte value from. + /// null if the value is null otherwise the byte value for the + /// attribute. + /// if the object in the attribute is not a byte. + /// if the attribute is a multi valued instead of single valued. + /// Since 1.4 + public static byte? GetByteValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (byte?)obj : null; + } + + /// + /// Gets the byte value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the byte value from. + /// null if the value is null otherwise the byte value for the + /// attribute. + /// if the object in the attribute is not a byte. + /// if the attribute is a multi valued instead of single valued. + /// Since 1.4 + public static byte[] GetByteArrayValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (byte[])obj : null; + } + + /// + /// Gets the boolean value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the boolean value from. + /// null if the value is null otherwise the boolean value for the + /// attribute. + /// if the object in the attribute is not a boolean. + /// if the attribute is a multi valued instead of single valued. + public static bool? GetBooleanValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (bool?)obj : null; + } + + /// + /// Gets the big decimal value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the big decimal value from. + /// null if the value is null otherwise the big decimal value for the + /// attribute. + /// if the object in the attribute is not a big decimal. + /// if the attribute is a multi valued instead of single valued. + /// Since 1.4 + public static BigDecimal GetBigDecimalValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (BigDecimal)obj : null; + } + + /// + /// Gets the big integer value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the big integer value from. + /// null if the value is null otherwise the big integer value for the + /// attribute. + /// if the object in the attribute is not a big integer. + /// if the attribute is a multi valued instead of single valued. + /// Since 1.4 + public static BigInteger GetBigIntegerValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (BigInteger)obj : null; + } + + /// + /// Gets the dictionary value from the single value attribute. + /// + /// ConnectorAttribute to retrieve the dictionary value from. + /// null if the value is null otherwise the dictionary value for the + /// attribute. + /// if the object in the attribute is not a dictionary. + /// if the attribute is a multi valued instead of single valued. + /// Since 1.4 + public static IDictionary GetDictionaryValue(ConnectorAttribute attr) + { + object obj = GetSingleValue(attr); + return obj != null ? (IDictionary)obj : null; + } + + /// + /// Get the single value from the ConnectorAttribute. + /// + /// + /// Return + /// null if the attribute's list of values is null or empty. + /// + public static object GetSingleValue(ConnectorAttribute attr) + { + Object ret = null; + IList val = attr.Value; + if (val != null && val.Count > 0) + { + // make sure this only called for single value.. + if (val.Count > 1) + { + const string MSG = "The method is only for single value attributes."; + throw new ArgumentException(MSG); + } + ret = val[0]; + } + return ret; + } + + /// + /// Transform a Collection of ConnectorAttribute} instances into a {@link Map}. + /// The key to each element in the map is the name of an ConnectorAttribute. + /// The value of each element in the map is the ConnectorAttribute instance with that name. + /// + /// + /// + public static IDictionary ToMap( + ICollection attributes) + { + IDictionary ret = + new Dictionary( + StringComparer.OrdinalIgnoreCase); + foreach (ConnectorAttribute attr in attributes) + { + ret[attr.Name] = attr; + } + return ret; + } + + /// + /// Get the from the attribute set. + /// + /// set of s that may contain a . + /// null if the set does not contain a object the first + /// one found. + public static Uid GetUidAttribute(ICollection attrs) + { + return (Uid)Find(Uid.NAME, attrs); + } + + /// + /// Filters out all special attributes from the set. + /// + /// + /// These special attributes + /// include , etc.. + /// + /// set of s to filter out the operational and + /// default attributes. + /// a set that only contains plain attributes or empty. + public static ICollection GetBasicAttributes(ICollection attrs) + { + ICollection ret = new HashSet(); + foreach (ConnectorAttribute attr in attrs) + { + // note this is dangerous because we need to be consistent + // in the naming of special attributes. + if (!IsSpecial(attr)) + { + ret.Add(attr); + } + } + return ret; + } + + /// + /// Filter out any basic attributes from the specified set, leaving only + /// special attributes. + /// + /// + /// Special attributes include , , + /// and . + /// + /// set of s to filter out the basic attributes + /// a set that only contains special attributes or an empty set if + /// there are none. + public static ICollection GetSpecialAttributes(ICollection attrs) + { + ICollection ret = new HashSet(); + foreach (ConnectorAttribute attr in attrs) + { + if (IsSpecial(attr)) + { + ret.Add(attr); + } + } + return ret; + } + + /// + /// Returns a mutable copy of the original set with the uid attribute removed. + /// + /// The original set. Must not be null. + /// A mutable copy of the original set with the uid attribute removed. + public static ICollection FilterUid(ICollection attrs) + { + Assertions.NullCheck(attrs, "attrs"); + HashSet ret = new HashSet(); + foreach (ConnectorAttribute attr in attrs) + { + if (!(attr is Uid)) + { + ret.Add(attr); + } + } + return ret; + } + + /// + /// Returns a mutable copy of the original set with the uid attribute added. + /// + /// The original set. Must not be null. + /// The uid. Must not be null. + /// A mutable copy of the original set with the uid attribute added. + public static ICollection AddUid(ICollection attrs, Uid uid) + { + Assertions.NullCheck(attrs, "attrs"); + Assertions.NullCheck(uid, "uid"); + HashSet ret = new HashSet(attrs); + ret.Add(uid); + return ret; + } + + /// + /// Determines if this attribute is a special attribute. + /// + /// + /// to test for against. + /// true if the attribute value is a , + /// , , or + /// . + /// if the attribute parameter is null. + public static bool IsSpecial(ConnectorAttribute attr) + { + // note this is dangerous because we need to be consistent + // in the naming of special attributes. + String name = attr.Name; + return IsSpecialName(name); + } + + /// + /// Determines if this attribute is a special attribute. + /// + /// + /// to test for against. + /// true if the attribute value is a , + /// , , or + /// . + /// if the attribute parameter is null. + public static bool IsSpecial(ConnectorAttributeInfo attr) + { + String name = attr.Name; + return IsSpecialName(name); + } + + /// + /// Determines whether the specified attribute name is special in the + /// sense of . + /// + /// the name of the attribute to test + /// true if the attribute name is special + public static bool IsSpecialName(String name) + { + return NameUtil.IsSpecialName(name); + } + + /// + /// Creates the special naming for operational type attributes. + /// + /// string to make special + /// name constructed for use as an operational attribute. + public static string CreateSpecialName(string name) + { + return NameUtil.CreateSpecialName(name); + } + + /// + /// Compares two attribute names for equality. + /// + /// the first attribute name + /// the second attribute name + /// true if the two attribute names are equal + public static bool NamesEqual(string name1, string name2) + { + return NameUtil.NamesEqual(name2, name2); + } + + /// + /// Gets the 'Name' attribute from a set of ConnectorAttributes. + /// + /// set of attributes to search against. + /// the 'Name' attribute it if exsist otherwisenull + public static Name GetNameFromAttributes(ICollection attrs) + { + return (Name)Find(Name.NAME, attrs); + } + + + /// + /// Find the of the given name in the . + /// + /// + /// 's name to search for. + /// + /// of attribute to search. + /// + /// with the specified otherwise null. + public static ConnectorAttribute Find(string name, ICollection attrs) + { + Assertions.NullCheck(name, "name"); + ICollection attributes = CollectionUtil.NullAsEmpty(attrs); + foreach (ConnectorAttribute attr in attributes) + { + if (attr.Is(name)) + { + return attr; + } + } + return null; + } + /// + /// Get the password value from the provided set of s. + /// + public static GuardedString GetPasswordValue(ICollection attrs) + { + ConnectorAttribute pwd = Find(OperationalAttributes.PASSWORD_NAME, attrs); + return (pwd == null) ? null : GetGuardedStringValue(pwd); + } + + /// + /// Get the current password value from the provided set of s. + /// + /// Set of s that may contain the current password + /// + /// . + /// + /// null if it does not exist in the else + /// the value. + public static GuardedString GetCurrentPasswordValue(ICollection attrs) + { + ConnectorAttribute pwd = Find(OperationalAttributes.CURRENT_PASSWORD_NAME, attrs); + return (pwd == null) ? null : GetGuardedStringValue(pwd); + } + /// + /// Determine if the is locked out. + /// + /// + /// By getting the + /// value of the . + /// + /// + /// object to inspect. + /// if the parameter 'obj' is null. + /// + /// null if the attribute does not exist otherwise to + /// value of the . + public static bool? IsLockedOut(ConnectorObject obj) + { + ConnectorAttribute attr = obj.GetAttributeByName(OperationalAttributes.LOCK_OUT_NAME); + return (attr == null) ? null : GetBooleanValue(attr); + } + + /// + /// Determine if the is enable. + /// + /// + /// By getting the value + /// of the . + /// + /// + /// object to inspect. + /// if the object does not contain attribute in question. + /// if the parameter 'obj' is null. + /// + /// null if the attribute does not exist otherwise to + /// value of the . + public static bool? IsEnabled(ConnectorObject obj) + { + ConnectorAttribute attr = obj.GetAttributeByName(OperationalAttributes.ENABLE_NAME); + return (attr == null) ? null : GetBooleanValue(attr); + } + + /// + /// Retrieve the password expiration date from the . + /// + /// + /// object to inspect. + /// if the object does not contain attribute in question. + /// if the parameter 'obj' is null. + /// + /// null if the does not exist + /// otherwise the value of the . + public static DateTime? GetPasswordExpirationDate(ConnectorObject obj) + { + DateTime? ret = null; + ConnectorAttribute attr = obj.GetAttributeByName(OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME); + if (attr != null) + { + long? date = GetLongValue(attr); + if (date != null) + { + ret = DateTime.FromFileTimeUtc(date.Value); + } + } + return ret; + } + /// + /// Get the password expired attribute from a of + /// s. + /// + /// set of attribute to find the expired password + /// . + /// + /// null if the attribute does not exist and the value + /// of the if it does. + public static bool? GetPasswordExpired(ICollection attrs) + { + ConnectorAttribute pwd = Find(OperationalAttributes.PASSWORD_EXPIRED_NAME, attrs); + return (pwd == null) ? null : GetBooleanValue(pwd); + } + + /// + /// Determine if the password is expired for this object. + /// + /// + /// that should contain a password expired + /// attribute. + /// + /// null if the attribute does not exist and the value + /// of the if it does. + public static bool? IsPasswordExpired(ConnectorObject obj) + { + ConnectorAttribute pwd = obj.GetAttributeByName(OperationalAttributes.PASSWORD_EXPIRED_NAME); + return (pwd == null) ? null : GetBooleanValue(pwd); + } + + /// + /// Get the enable date from the set of attributes. + /// + /// set of attribute to find the enable date + /// . + /// + /// null if the attribute does not exist and the value + /// of the if it does. + public static DateTime? GetEnableDate(ICollection attrs) + { + ConnectorAttribute attr = Find(OperationalAttributes.ENABLE_DATE_NAME, attrs); + return (attr == null) ? null : GetDateTimeValue(attr); + } + } + #endregion + + #region ConnectorAttributeInfoUtil + public static class ConnectorAttributeInfoUtil + { + /// + /// Transform a Collection of instances into + /// a . + /// + /// + /// The key to each element in the map is the name of + /// an AttributeInfo. The value of each element in the map is the + /// AttributeInfo instance with that name. + /// + /// set of AttributeInfo to transform to a map. + /// a map of string and AttributeInfo. + /// if the parameter attributes is + /// null. + public static IDictionary ToMap( + ICollection attributes) + { + IDictionary + ret = new Dictionary( + StringComparer.OrdinalIgnoreCase); + foreach (ConnectorAttributeInfo attr in attributes) + { + ret[attr.Name] = attr; + } + return ret; + } + + /// + /// Find the of the given name in the . + /// + /// + /// 's name to search for. + /// + /// of AttributeInfo to search. + /// + /// with the specified otherwise null. + public static ConnectorAttributeInfo Find(string name, ICollection attrs) + { + Assertions.NullCheck(name, "name"); + ICollection attributes = CollectionUtil.NullAsEmpty(attrs); + foreach (ConnectorAttributeInfo attr in attributes) + { + if (attr.Is(name)) + { + return attr; + } + } + return null; + } + } + #endregion + + #region BigDecimal + /// + /// Placeholder since C# doesn't have a BigInteger + /// + public sealed class BigDecimal + { + private BigInteger _unscaledVal; + private int _scale; + public BigDecimal(BigInteger unscaledVal, + int scale) + { + if (unscaledVal == null) + { + throw new ArgumentNullException(); + } + _unscaledVal = unscaledVal; + _scale = scale; + } + public BigInteger UnscaledValue + { + get + { + return _unscaledVal; + } + } + public int Scale + { + get + { + return _scale; + } + } + public override bool Equals(object o) + { + BigDecimal other = o as BigDecimal; + if (other != null) + { + return UnscaledValue.Equals(other.UnscaledValue) && + Scale == other.Scale; + } + return false; + } + public override int GetHashCode() + { + return _unscaledVal.GetHashCode(); + } + + public override string ToString() + { + return UnscaledValue.ToString(); + } + } + #endregion + + #region BigInteger + /// + /// Placeholder since C# doesn't have a BigInteger + /// + public sealed class BigInteger + { + private string _value; + public BigInteger(string val) + { + if (val == null) + { + throw new ArgumentNullException(); + } + _value = val; + } + public string Value + { + get + { + return _value; + } + } + public override bool Equals(object o) + { + BigInteger other = o as BigInteger; + if (other != null) + { + return Value.Equals(other.Value); + } + return false; + } + public override int GetHashCode() + { + return _value.GetHashCode(); + } + public override string ToString() + { + return _value; + } + } + #endregion + + #region ConnectorAttribute + /// + /// Represents a named collection of values within a resource object, + /// although the simplest case is a name-value pair (e.g., email, + /// employeeID). Values can be empty, null, or set with various types. + /// Empty and null are supported because it makes a difference on some + /// resources (in particular database resources). The developer of a + /// Connector will use an builder to construct an instance of + /// ConnectorAttribute. + /// + public class ConnectorAttribute + { + private readonly string _name; + private readonly IList _value; + + internal ConnectorAttribute(string name, IList val) + { + if (StringUtil.IsBlank(name)) + { + throw new ArgumentException("Name must not be blank!"); + } + if (OperationalAttributes.PASSWORD_NAME.Equals(name) || + OperationalAttributes.CURRENT_PASSWORD_NAME.Equals(name)) + { + // check the value.. + if (val == null || val.Count != 1) + { + String MSG = "Must be a single value."; + throw new ArgumentException(MSG); + } + if (!(val[0] is GuardedString)) + { + const string MSG = "Password value must be an instance of GuardedString."; + throw new ArgumentException(MSG); + } + } + _name = name; + // copy to prevent corruption preserve null + _value = (val == null) ? null : CollectionUtil.NewReadOnlyList(val); + } + + public string Name + { + get + { + return _name; + } + } + + public IList Value + { + get + { + return _value; + } + } + + public bool Is(string name) + { + return NameUtil.NamesEqual(_name, name); + } + + public sealed override bool Equals(Object obj) + { + // test identity + if (this == obj) + { + return true; + } + // test for null.. + if (obj == null) + { + return false; + } + // test that the exact class matches + if (!(GetType().Equals(obj.GetType()))) + { + return false; + } + // test name field.. + ConnectorAttribute other = (ConnectorAttribute)obj; + if (!Is(other._name)) + { + return false; + } + + if (!CollectionUtil.Equals(_value, other._value)) + { + return false; + } + return true; + } + + public sealed override int GetHashCode() + { + return NameUtil.GetNameHashCode(_name); + } + + + public override string ToString() + { + // poor man's consistent toString impl.. + StringBuilder bld = new StringBuilder(); + bld.Append("ConnectorAttribute: "); + bld.Append(Name).Append(" = ").Append(CollectionUtil.Dump(Value)); + return bld.ToString(); + } + + public string GetDetails() + { + StringBuilder bld = new StringBuilder(); + bld.Append("ConnectorAttribute: Name='").Append(Name).Append("', Value(s)='").Append(CollectionUtil.Dump(Value)).Append("'"); + return bld.ToString(); + } + } + #endregion + + #region ConnectorAttributeBuilder + public sealed class ConnectorAttributeBuilder + { + private const String NAME_ERROR = "Name must not be blank!"; + + private string _name; + private IList _value; + + public ConnectorAttributeBuilder() + { + } + public static ConnectorAttribute Build(String name) + { + return new ConnectorAttributeBuilder() { Name = name }.Build(); + } + public static ConnectorAttribute Build(String name, + params Object[] args) + { + ConnectorAttributeBuilder bld = new ConnectorAttributeBuilder(); + bld.Name = name; + bld.AddValue(args); + return bld.Build(); + } + public static ConnectorAttribute Build(String name, + ICollection val) + { + ConnectorAttributeBuilder bld = new ConnectorAttributeBuilder(); + bld.Name = name; + bld.AddValue(val); + return bld.Build(); + } + + public string Name + { + get + { + return _name; + } + set + { + if (StringUtil.IsBlank(value)) + { + throw new ArgumentException(NAME_ERROR); + } + _name = value; + } + } + + public IList Value + { + get + { + return _value == null ? null : CollectionUtil.AsReadOnlyList(_value); + } + } + + public ConnectorAttributeBuilder AddValue(params Object[] args) + { + AddValuesInternal(args); + return this; + } + public ConnectorAttributeBuilder AddValue(ICollection values) + { + AddValuesInternal(values); + return this; + } + + public ConnectorAttribute Build() + { + if (StringUtil.IsBlank(Name)) + { + throw new ArgumentException(NAME_ERROR); + } + if (Uid.NAME.Equals(_name)) + { + return new Uid(GetSingleStringValue()); + } + else if (Org.IdentityConnectors.Framework.Common.Objects.Name.NAME.Equals(_name)) + { + return new Name(GetSingleStringValue()); + } + return new ConnectorAttribute(Name, _value); + } + private void CheckSingleValue() + { + if (_value == null || _value.Count != 1) + { + const String MSG = "Must be a single value."; + throw new ArgumentException(MSG); + } + } + private String GetSingleStringValue() + { + CheckSingleValue(); + if (!(_value[0] is String)) + { + const String MSG = "Must be single string value."; + throw new ArgumentException(MSG); + } + return (String)_value[0]; + } + private void AddValuesInternal(IEnumerable values) + { + if (values != null) + { + // make sure the list is ready to receive values. + if (_value == null) + { + _value = new List(); + } + // add each value checking to make sure its correct + foreach (Object v in values) + { + FrameworkUtil.CheckAttributeValue(Name, v); + _value.Add(v); + } + } + } + + // ======================================================================= + // Operational Attributes + // ======================================================================= + /// + /// Builds an password expiration date . + /// + /// + /// This + /// represents the date/time a password will expire on a + /// resource. + /// + /// UTC time in milliseconds. + /// an built with the pre-defined name for password + /// expiration date. + public static ConnectorAttribute BuildPasswordExpirationDate(DateTime dateTime) + { + return BuildPasswordExpirationDate(DateTimeUtil.GetUtcTimeMillis(dateTime)); + } + + /// + /// Builds an password expiration date . + /// + /// + /// This + /// represents the date/time a password will expire on a + /// resource. + /// + /// UTC time in milliseconds. + /// an built with the pre-defined name for password + /// expiration date. + public static ConnectorAttribute BuildPasswordExpirationDate(long dateTime) + { + return Build(OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME, + dateTime); + } + + /// + /// Builds the operational attribute password. + /// + /// the string that represents a password. + /// an attribute that represents a password. + public static ConnectorAttribute BuildPassword(GuardedString password) + { + return Build(OperationalAttributes.PASSWORD_NAME, password); + } + + /// + /// Builds the operational attribute current password. + /// + /// + /// The current password + /// indicates this a password change by the account owner and not an + /// administrator. The use case is that an administrator password change may + /// not keep history or validate against policy. + /// + /// the string that represents a password. + /// an attribute that represents a password. + public static ConnectorAttribute BuildCurrentPassword(GuardedString password) + { + return Build(OperationalAttributes.CURRENT_PASSWORD_NAME, password); + } + + public static ConnectorAttribute BuildPassword(SecureString password) + { + return Build(OperationalAttributes.PASSWORD_NAME, new GuardedString(password)); + } + public static ConnectorAttribute BuildCurrentPassword(SecureString password) + { + return Build(OperationalAttributes.CURRENT_PASSWORD_NAME, new GuardedString(password)); + } + /// + /// Builds ant operational attribute that either represents the object is + /// enabled or sets in disabled depending on where its used for instance on + /// it could be used to create a disabled account. + /// + /// + /// In + /// it would show the object is enabled or disabled. + /// + /// true indicates the object is enabled otherwise false. + /// + /// that determines the enable/disable state of an + /// object. + public static ConnectorAttribute BuildEnabled(bool val) + { + return Build(OperationalAttributes.ENABLE_NAME, val); + } + + /// + /// Builds out an operational that determines the enable + /// date for an object. + /// + /// The date and time to enable a particular object, or the date + /// time an object will be enabled. + /// + /// + /// + public static ConnectorAttribute BuildEnableDate(DateTime date) + { + return BuildEnableDate(DateTimeUtil.GetUtcTimeMillis(date)); + } + + /// + /// Builds out an operational that determines the enable + /// date for an object. + /// + /// + /// The time parameter is UTC in milliseconds. + /// + /// The date and time to enable a particular object, or the date + /// time an object will be enabled. + /// + /// + /// + public static ConnectorAttribute BuildEnableDate(long date) + { + return Build(OperationalAttributes.ENABLE_DATE_NAME, date); + } + + /// + /// Builds out an operational that determines the disable + /// date for an object. + /// + /// The date and time to enable a particular object, or the date + /// time an object will be enabled. + /// + /// + /// + public static ConnectorAttribute BuildDisableDate(DateTime date) + { + return BuildDisableDate(DateTimeUtil.GetUtcTimeMillis(date)); + } + + /// + /// Builds out an operational that determines the disable + /// date for an object. + /// + /// + /// The time parameter is UTC in milliseconds. + /// + /// The date and time to enable a particular object, or the date + /// time an object will be enabled. + /// + /// + /// + public static ConnectorAttribute BuildDisableDate(long date) + { + return Build(OperationalAttributes.DISABLE_DATE_NAME, date); + } + + /// + /// Builds the lock attribute that determines if an object is locked out. + /// + /// true if the object is locked otherwise false. + /// + /// that represents the lock state of an object. + public static ConnectorAttribute BuildLockOut(bool lck) + { + return Build(OperationalAttributes.LOCK_OUT_NAME, lck); + } + + /// + /// Builds out an operational that determines if a password + /// is expired or expires a password. + /// + /// from the API true expires and from the SPI its shows its + /// either expired or not. + /// + /// + /// + public static ConnectorAttribute BuildPasswordExpired(bool expired) + { + return Build(OperationalAttributes.PASSWORD_EXPIRED_NAME, expired); + } + + // ======================================================================= + // Pre-defined Attributes + // ======================================================================= + + /// + /// Builds out a pre-defined that determines the last login + /// date for an object. + /// + /// The date and time of the last login. + /// + /// + /// + public static ConnectorAttribute BuildLastLoginDate(DateTime date) + { + return BuildLastLoginDate(DateTimeUtil.GetUtcTimeMillis(date)); + } + + /// + /// Builds out a pre-defined that determines the last login + /// date for an object. + /// + /// + /// The time parameter is UTC in milliseconds. + /// + /// The date and time of the last login. + /// + /// + /// + public static ConnectorAttribute BuildLastLoginDate(long date) + { + return Build(PredefinedAttributes.LAST_LOGIN_DATE_NAME, date); + } + + /// + /// Builds out a pre-defined that determines the last + /// password change date for an object. + /// + /// The date and time the password was changed. + /// + /// + /// + public static ConnectorAttribute BuildLastPasswordChangeDate(DateTime date) + { + return BuildLastPasswordChangeDate(DateTimeUtil.GetUtcTimeMillis(date)); + } + + /// + /// Builds out a pre-defined that determines the last + /// password change date for an object. + /// + /// The date and time the password was changed. + /// + /// + /// + public static ConnectorAttribute BuildLastPasswordChangeDate(long date) + { + return Build(PredefinedAttributes.LAST_PASSWORD_CHANGE_DATE_NAME, date); + } + + /// + /// Common password policy attribute where the password must be changed every + /// so often. + /// + /// + /// The value for this attribute is milliseconds since its the + /// lowest common denominator. + /// + public static ConnectorAttribute BuildPasswordChangeInterval(long val) + { + return Build(PredefinedAttributes.PASSWORD_CHANGE_INTERVAL_NAME, val); + } + } + #endregion + + #region ConnectorMessages + /// + /// Message catalog for a given connector. + /// + public interface ConnectorMessages + { + /// + /// Formats the given message key in the current UI culture. + /// + /// The message key to format. + /// The default message if key is not found. If null, defaults + /// to key. + /// Parameters with which to format the message. + /// The formatted string. + String Format(String key, String dflt, params object[] args); + } + #endregion + + #region ConnectorObject + public sealed class ConnectorObject + { + private readonly ObjectClass _objectClass; + private readonly IDictionary _attrs; + public ConnectorObject(ObjectClass objectClass, ICollection attrs) + { + if (objectClass == null) + { + throw new ArgumentException("ObjectClass may not be null"); + } + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new System.ArgumentException("Connector object class can not be type of __ALL__"); + } + if (attrs == null || attrs.Count == 0) + { + throw new ArgumentException("attrs cannot be empty or null."); + } + _objectClass = objectClass; + _attrs = + CollectionUtil.NewReadOnlyDictionary(attrs, + value => { return value.Name; }); + if (!_attrs.ContainsKey(Uid.NAME)) + { + const String MSG = "The ConnectorAttribute set must contain a Uid."; + throw new ArgumentException(MSG); + } + if (!_attrs.ContainsKey(Name.NAME)) + { + const string MSG = "The ConnectorAttribute set must contain a Name."; + throw new ArgumentException(MSG); + } + } + public ICollection GetAttributes() + { + return _attrs.Values; + } + public ConnectorAttribute GetAttributeByName(string name) + { + return CollectionUtil.GetValue(_attrs, name, null); + } + public Uid Uid + { + get + { + return (Uid)GetAttributeByName(Uid.NAME); + } + } + public Name Name + { + get + { + return (Name)GetAttributeByName(Name.NAME); + } + } + public ObjectClass ObjectClass + { + get + { + return _objectClass; + } + } + public override int GetHashCode() + { + return CollectionUtil.GetHashCode(_attrs); + } + public override bool Equals(Object o) + { + ConnectorObject other = o as ConnectorObject; + if (other != null) + { + if (!_objectClass.Equals(other.ObjectClass)) + { + return false; + } + return CollectionUtil.Equals(_attrs, other._attrs); + } + return false; + } + } + #endregion + + #region ConnectorObjectBuilder + public sealed class ConnectorObjectBuilder + { + private ObjectClass _objectClass; + private IDictionary _attributes; + public ConnectorObjectBuilder() + { + _attributes = new Dictionary(); + // default always add the account object class.. + ObjectClass = ObjectClass.ACCOUNT; + } + + public void SetUid(string uid) + { + AddAttribute(new Uid(uid)); + } + + public void SetUid(Uid uid) + { + AddAttribute(uid); + } + + public void SetName(string name) + { + AddAttribute(new Name(name)); + } + + public void SetName(Name name) + { + AddAttribute(name); + } + + public ObjectClass ObjectClass + { + get + { + return _objectClass; + } + set + { + if (ObjectClass.ALL.Equals(value)) + { + throw new System.ArgumentException("Connector object class can not be type of __ALL__"); + } + _objectClass = value; + } + } + + + + // ======================================================================= + // Clone basically.. + // ======================================================================= + /// + /// Takes all the attribute from a and add/overwrite + /// the current attributes. + /// + public ConnectorObjectBuilder Add(ConnectorObject obj) + { + // simply add all the attributes it will include (Uid, ObjectClass..) + foreach (ConnectorAttribute attr in obj.GetAttributes()) + { + AddAttribute(attr); + } + ObjectClass = obj.ObjectClass; + return this; + } + + public ConnectorObjectBuilder AddAttribute(params ConnectorAttribute[] attrs) + { + ValidateParameter(attrs, "attrs"); + foreach (ConnectorAttribute a in attrs) + { + //DONT use Add - it throws exceptions if already there + _attributes[a.Name] = a; + } + return this; + } + public ConnectorObjectBuilder AddAttributes(ICollection attrs) + { + ValidateParameter(attrs, "attrs"); + foreach (ConnectorAttribute a in attrs) + { + _attributes[a.Name] = a; + } + return this; + } + /// + /// Adds values to the attribute. + /// + public ConnectorObjectBuilder AddAttribute(String name, params object[] objs) + { + AddAttribute(ConnectorAttributeBuilder.Build(name, objs)); + return this; + } + + /// + /// Adds each object in the collection. + /// + public ConnectorObjectBuilder AddAttribute(String name, ICollection obj) + { + AddAttribute(ConnectorAttributeBuilder.Build(name, obj)); + return this; + } + public ConnectorObject Build() + { + // check that there are attributes to return.. + if (_attributes.Count == 0) + { + throw new InvalidOperationException("No attributes set!"); + } + return new ConnectorObject(ObjectClass, _attributes.Values); + } + private static void ValidateParameter(Object param, String paramName) + { + if (param == null) + { + String FORMAT = "Parameter " + param + " must not be null!"; + throw new NullReferenceException(FORMAT); + } + } + } + #endregion + + #region ConnectorAttributeInfo + public sealed class ConnectorAttributeInfo + { + private readonly string _name; + private readonly Type _type; + private readonly Flags _flags; + + /// + /// Enum of modifier flags to use for attributes. + /// + /// + /// Note that + /// this enum is designed for configuration by exception such that + /// an empty set of flags are the defaults: + /// + /// + /// updateable + /// + /// + /// + /// creatable + /// + /// + /// + /// returned by default + /// + /// + /// + /// readable + /// + /// + /// + /// single-valued + /// + /// + /// + /// optional + /// + /// + /// + /// + [FlagsAttribute] + public enum Flags + { + NONE = 0, + REQUIRED = 1, + MULTIVALUED = 2, + NOT_CREATABLE = 4, + NOT_UPDATEABLE = 8, + NOT_READABLE = 16, + NOT_RETURNED_BY_DEFAULT = 32 + } + + internal ConnectorAttributeInfo(string name, Type type, + Flags flags) + { + if (StringUtil.IsBlank(name)) + { + throw new ArgumentException("Name must not be blank!"); + } + if ((OperationalAttributes.PASSWORD_NAME.Equals(name) || + OperationalAttributes.CURRENT_PASSWORD_NAME.Equals(name)) && + !typeof(GuardedString).Equals(type)) + { + String MSG = "Password based attributes must be of type GuardedString."; + throw new ArgumentException(MSG); + } + // check the type.. + FrameworkUtil.CheckAttributeType(type); + _name = name; + _type = type; + _flags = flags; + if (!IsReadable && IsReturnedByDefault) + { + throw new ArgumentException("Attribute " + name + " is flagged as not-readable, so it should also be as not-returned-by-default."); + } + } + + + /// + /// The native name of the attribute. + /// + /// the native name of the attribute its describing. + public string Name + { + get + { + return _name; + } + } + + /// + /// The basic type associated with this attribute. + /// + /// + /// All primitives are + /// supported. + /// + /// the native type if uses. + public Type ValueType + { + get + { + return _type; + } + } + + /// + /// Returns the set of flags associated with the attribute. + /// + /// the set of flags associated with the attribute + public Flags InfoFlags + { + get + { + return _flags; + } + } + + + public bool Is(string name) + { + return NameUtil.NamesEqual(_name, name); + } + + /// + /// Determines if the attribute is readable. + /// + /// true if the attribute is readable else false. + public bool IsReadable + { + get + { + return (_flags & Flags.NOT_READABLE) == 0; + } + } + + /// + /// Determines if the attribute is writable on create. + /// + /// true if the attribute is writable on create else false. + public bool IsCreatable + { + get + { + return (_flags & Flags.NOT_CREATABLE) == 0; + } + } + + /// + /// Determines if the attribute is writable on update. + /// + /// true if the attribute is writable on update else false. + public bool IsUpdateable + { + get + { + return (_flags & Flags.NOT_UPDATEABLE) == 0; + } + } + + /// + /// Determines whether this attribute is required for creates. + /// + /// true if the attribute is required for an object else false. + public bool IsRequired + { + get + { + return (_flags & Flags.REQUIRED) != 0; + } + } + + /// + /// Determines if this attribute can handle multiple values. + /// + /// + /// There is a + /// special case with byte[] since in most instances this denotes a single + /// object. + /// + /// true if the attribute is multi-value otherwise false. + public bool IsMultiValued + { + get + { + return (_flags & Flags.MULTIVALUED) != 0; + } + } + + /// + /// Determines if the attribute is returned by default. + /// + /// + /// Indicates if an + /// will be returned during or + /// inside a by default. The default + /// value is true. + /// + /// false if the attribute should not be returned by default. + public bool IsReturnedByDefault + { + get + { + return (_flags & Flags.NOT_RETURNED_BY_DEFAULT) == 0; + } + } + + public override bool Equals(Object o) + { + ConnectorAttributeInfo other = o as ConnectorAttributeInfo; + if (other != null) + { + if (!Is(other.Name)) + { + return false; + } + if (!ValueType.Equals(other.ValueType)) + { + return false; + } + if (_flags != other._flags) + { + return false; + } + return true; + } + return false; + } + + public override int GetHashCode() + { + return NameUtil.GetNameHashCode(_name); + } + + public override string ToString() + { + return SerializerUtil.SerializeXmlObject(this, false); + } + } + #endregion + + #region ConnectorAttributeInfoBuilder + /// + /// Simplifies the process of building 'AttributeInfo' objects. + /// + /// + /// This class is + /// responsible for providing a default implementation of . + /// + /// AttributeInfoBuilder bld = new AttributeInfoBuilder("email"); + /// bld.setRequired(true); + /// AttributeInfo info = bld.build(); + /// + /// + /// Will Droste + /// $Revision: 1.9 $ + /// 1.0 + public sealed class ConnectorAttributeInfoBuilder + { + + private String _name; + private Type _type; + private ConnectorAttributeInfo.Flags _flags; + + /// + /// Creates an builder with all the defaults set. + /// + /// + /// The name must be set before + /// the 'build' method is called otherwise an + /// is thrown. + ///
+        /// Name: <not set>
+        /// Readable: true
+        /// Writeable: true
+        /// Required: false
+        /// Type: string
+        /// MultiValue: false
+        /// 
+ ///
+ public ConnectorAttributeInfoBuilder() + { + ValueType = (typeof(String)); + _flags = ConnectorAttributeInfo.Flags.NONE; + } + + /// + /// Creates an builder with all the defaults set. + /// + /// + /// The name must be set before + /// the 'build' method is called otherwise an + /// is thrown. + ///
+        /// Name: <not set>
+        /// Readable: true
+        /// Writeable: true
+        /// Required: false
+        /// Type: string
+        /// MultiValue: false
+        /// 
+ ///
+ public ConnectorAttributeInfoBuilder(String name) + : this(name, typeof(String)) + { + } + + /// + /// Creates an builder with all the defaults set. + /// + /// + /// The name must be set before + /// the 'build' method is called otherwise an + /// is thrown. + ///
+        /// Name: <not set>
+        /// Readable: true
+        /// Writeable: true
+        /// Required: false
+        /// Type: string
+        /// MultiValue: false
+        /// 
+ ///
+ public ConnectorAttributeInfoBuilder(String name, Type type) + { + Name = (name); + ValueType = (type); + //noneOf means the defaults + _flags = ConnectorAttributeInfo.Flags.NONE; + } + + /// + /// Builds an object based on the properties set. + /// + /// + /// based on the properties set. + public ConnectorAttributeInfo Build() + { + return new ConnectorAttributeInfo(_name, _type, _flags); + } + + /// + /// Sets the unique name of the object. + /// + /// unique name of the object. + public String Name + { + set + { + if (StringUtil.IsBlank(value)) + { + throw new ArgumentException("Argument must not be blank."); + } + _name = value; + } + } + + /// + /// Sets the unique name of the object. + /// + /// unique name of the object. + public ConnectorAttributeInfoBuilder SetName(string name) + { + if (StringUtil.IsBlank(name)) + { + throw new ArgumentException("Argument must not be blank."); + } + _name = name; + return this; + } + + /// + /// Please see for the + /// definitive list of supported types. + /// + /// type for an 's value. + /// if the Class is not a supported type. + public Type ValueType + { + set + { + FrameworkUtil.CheckAttributeType(value); + _type = value; + } + } + + /// + /// Please see for the + /// definitive list of supported types. + /// + /// type for an 's value. + /// if the Class is not a supported type. + public ConnectorAttributeInfoBuilder SetValueType(Type type) + { + FrameworkUtil.CheckAttributeType(type); + _type = type; + return this; + } + + /// + /// Determines if the attribute is readable. + /// + public bool Readable + { + set + { + SetFlag(ConnectorAttributeInfo.Flags.NOT_READABLE, !value); + } + } + + /// + /// Determines if the attribute is readable. + /// + public ConnectorAttributeInfoBuilder SetReadable(bool value) + { + SetFlag(ConnectorAttributeInfo.Flags.NOT_READABLE, !value); + return this; + } + + /// + /// Determines if the attribute is writable. + /// + public bool Creatable + { + set + { + SetFlag(ConnectorAttributeInfo.Flags.NOT_CREATABLE, !value); + } + } + + /// + /// Determines if the attribute is writable. + /// + public ConnectorAttributeInfoBuilder SetCreatable(bool value) + { + SetFlag(ConnectorAttributeInfo.Flags.NOT_CREATABLE, !value); + return this; + } + + /// + /// Determines if this attribute is required. + /// + public bool Required + { + set + { + SetFlag(ConnectorAttributeInfo.Flags.REQUIRED, value); + } + } + + /// + /// Determines if this attribute is required. + /// + public ConnectorAttributeInfoBuilder SetRequired(bool value) + { + SetFlag(ConnectorAttributeInfo.Flags.REQUIRED, value); + return this; + } + + /// + /// Determines if this attribute supports multivalue. + /// + public bool MultiValued + { + set + { + SetFlag(ConnectorAttributeInfo.Flags.MULTIVALUED, value); + } + } + + /// + /// Determines if this attribute supports multivalue. + /// + public ConnectorAttributeInfoBuilder SetMultiValued(bool value) + { + SetFlag(ConnectorAttributeInfo.Flags.MULTIVALUED, value); + return this; + } + + /// + /// Determines if this attribute writable during update. + /// + public bool Updateable + { + set + { + SetFlag(ConnectorAttributeInfo.Flags.NOT_UPDATEABLE, !value); + } + } + + /// + /// Determines if this attribute writable during update. + /// + public ConnectorAttributeInfoBuilder SetUpdateable(bool value) + { + SetFlag(ConnectorAttributeInfo.Flags.NOT_UPDATEABLE, !value); + return this; + } + + public bool ReturnedByDefault + { + set + { + SetFlag(ConnectorAttributeInfo.Flags.NOT_RETURNED_BY_DEFAULT, !value); + } + } + + public ConnectorAttributeInfoBuilder SetReturnedByDefault(bool value) + { + SetFlag(ConnectorAttributeInfo.Flags.NOT_RETURNED_BY_DEFAULT, !value); + return this; + } + + /// + /// Sets all of the flags for this builder. + /// + /// The set of attribute info flags. Null means clear all flags. + /// + /// NOTE: EnumSet.noneOf(AttributeInfo.Flags.class) results in + /// an attribute with the default behavior: + /// + /// + /// updateable + /// + /// + /// + /// creatable + /// + /// + /// + /// returned by default + /// + /// + /// + /// readable + /// + /// + /// + /// single-valued + /// + /// + /// + /// optional + /// + /// + /// + /// + /// + public ConnectorAttributeInfo.Flags InfoFlags + { + set + { + _flags = value; + } + } + + private void SetFlag(ConnectorAttributeInfo.Flags flag, bool value) + { + if (value) + { + _flags = _flags | flag; + } + else + { + _flags = _flags & ~flag; + } + } + + /// + /// Convenience method to create an AttributeInfo. + /// + /// + /// Equivalent to + /// + /// new AttributeInfoBuilder(name,type).setFlags(flags).build() + /// + /// + /// The name of the attribute + /// The type of the attribute + /// The flags for the attribute. Null means clear all flags + /// The attribute info + public static ConnectorAttributeInfo Build(String name, Type type, + ConnectorAttributeInfo.Flags flags) + { + return new ConnectorAttributeInfoBuilder(name, type) { InfoFlags = flags }.Build(); + } + /// + /// Convenience method to create an AttributeInfo. + /// + /// + /// Equivalent to + /// + /// AttributeInfoBuilder.build(name,type,null) + /// + /// + /// The name of the attribute + /// The type of the attribute + /// The attribute info + public static ConnectorAttributeInfo Build(String name, Type type) + { + return Build(name, type, ConnectorAttributeInfo.Flags.NONE); + } + + /// + /// Convenience method to create an AttributeInfo. + /// + /// + /// Equivalent to + /// + /// AttributeInfoBuilder.build(name,type) + /// + /// + /// The name of the attribute + /// The attribute info + public static ConnectorAttributeInfo Build(String name) + { + return Build(name, typeof(String)); + } + + /// + /// Convenience method to create a new AttributeInfoBuilder. + /// + /// Equivalent to: new AttributeInfoBuilder(name, String.class) + /// + /// + /// The name of the attribute + /// The attribute info builder with predefined name and type value. + /// Since 1.4 + public static ConnectorAttributeInfoBuilder Define(string name) + { + return new ConnectorAttributeInfoBuilder(name, typeof(string)); + } + + /// + /// Convenience method to create a new AttributeInfoBuilder. + /// + /// Equivalent to: new AttributeInfoBuilder(name, type) + /// + /// + /// The name of the attribute + /// + /// The type of the attribute + /// The attribute info builder with predefined name and type value. + /// Since 1.4 + public static ConnectorAttributeInfoBuilder Define(string name, Type type) + { + return new ConnectorAttributeInfoBuilder(name, type); + } + } + #endregion + + #region FileName + /// + /// Placeholder for java.io.File since C#'s + /// FileInfo class throws exceptions if the + /// file doesn't exist. + /// + public sealed class FileName + { + private string _path; + public FileName(string path) + { + if (path == null) + { + throw new ArgumentNullException(); + } + _path = path; + } + public string Path + { + get + { + return _path; + } + } + public override bool Equals(object o) + { + FileName other = o as FileName; + if (other != null) + { + return Path.Equals(other.Path); + } + return false; + } + public override int GetHashCode() + { + return _path.GetHashCode(); + } + public override string ToString() + { + return _path; + } + } + #endregion + + #region Name + public sealed class Name : ConnectorAttribute + { + public readonly static string NAME = ConnectorAttributeUtil.CreateSpecialName("NAME"); + public readonly static ConnectorAttributeInfo INFO = + new ConnectorAttributeInfoBuilder(NAME) { Required = true }.Build(); + + public Name(String value) + : base(NAME, CollectionUtil.NewReadOnlyList(value)) + { + } + + /// + /// The single value of the attribute that is the unique id of an object. + /// + /// value that identifies an object. + public String GetNameValue() + { + return ConnectorAttributeUtil.GetStringValue(this); + } + } + #endregion + + #region ObjectClassUtil + public static class ObjectClassUtil + { + /// + /// Determines if this object class is a special object class. + /// Special object classes include the predefined ones, such as + /// and . + /// + /// the object class to test + /// true if the object class is special + /// if the object class parameter is null + public static bool IsSpecial(ObjectClass oclass) + { + String name = oclass.GetObjectClassValue(); + return IsSpecialName(name); + } + + /// + /// Determines whether the specified object class name is special in the + /// sense of . + /// + /// the name of the object class to test + /// true if the object class name is special + public static bool IsSpecialName(String name) + { + return NameUtil.IsSpecialName(name); + } + + /// + /// Create a special name from the specified name. Add the __ + /// string as both prefix and suffix. This indicates that a name + /// identifies a special object class such as a predefined one. + /// + /// object class name to make special + /// name constructed for use as a special name + public static string CreateSpecialName(string name) + { + return NameUtil.CreateSpecialName(name); + } + + /// + /// Compares two object class names for equality. + /// + /// the first object class name + /// the second object class name + /// true if the two object class names are equal + public static bool NamesEqual(string name1, string name2) + { + return NameUtil.NamesEqual(name2, name2); + } + } + #endregion + + #region ObjectClass + public sealed class ObjectClass + { + /// + /// This constant defines a specific {@link #getObjectClassValue value + /// of ObjectClass} that is reserved for . + /// + public static readonly String ACCOUNT_NAME = ObjectClassUtil.CreateSpecialName("ACCOUNT"); + + /// + /// This constant defines a specific {@link #getObjectClassValue value + /// of ObjectClass} that is reserved for . + /// + public static readonly String GROUP_NAME = ObjectClassUtil.CreateSpecialName("GROUP"); + /// + /// This constant defines a specific {@link #getObjectClassValue value + /// of ObjectClass} that is reserved for . + /// + public static readonly String ALL_NAME = ObjectClassUtil.CreateSpecialName("ALL"); + /// + /// Denotes an account based object. + /// + public static readonly ObjectClass ACCOUNT = new ObjectClass(ACCOUNT_NAME); + /// + /// Denotes a group based object. + /// + public static readonly ObjectClass GROUP = new ObjectClass(GROUP_NAME); + /// + /// Represents all collections that contains any object. + /// + /// This constant allowed to use in operation + /// + /// and + /// + /// any other operation throws + /// + /// + public static readonly ObjectClass ALL = new ObjectClass(ALL_NAME); + + private readonly String _type; + + public ObjectClass(String type) + { + if (type == null) + { + throw new ArgumentException("Type cannot be null."); + } + _type = type; + } + public String GetObjectClassValue() + { + return _type; + } + + public String Type + { + get { return _type; } + } + + /// + /// Convenience method to build the display name key for + /// an object class. + /// + /// The display name key. + public String GetDisplayNameKey() + { + return "MESSAGE_OBJECT_CLASS_" + _type.ToUpper(CultureInfo.GetCultureInfo("en-US")); + } + + /// + /// Determines if the 'name' matches this . + /// + /// case-insensitive string representation of the ObjectClass's + /// type. + /// + /// true if the case-insensitive name is equal to + /// that of the one in this . + public bool Is(String name) + { + return NameUtil.NamesEqual(_type, name); + } + + public override int GetHashCode() + { + return NameUtil.GetNameHashCode(_type); + } + + public override bool Equals(object obj) + { + // test identity + if (this == obj) + { + return true; + } + + // test for null.. + if (obj == null) + { + return false; + } + + // test that the exact class matches + if (!(GetType() == obj.GetType())) + { + return false; + } + + ObjectClass other = (ObjectClass)obj; + + if (!Is(other._type)) + { + return false; + } + + return true; + } + + public override string ToString() + { + return "ObjectClass: " + _type; + } + } + #endregion + + #region ObjectClassInfo + public sealed class ObjectClassInfo + { + private readonly String _type; + private readonly ICollection _info; + private readonly bool _isContainer; + + public ObjectClassInfo(String type, + ICollection attrInfo, + bool isContainer) + { + Assertions.NullCheck(type, "type"); + _type = type; + _info = CollectionUtil.NewReadOnlySet(attrInfo); + _isContainer = isContainer; + // check to make sure name exists + IDictionary dict + = ConnectorAttributeInfoUtil.ToMap(attrInfo); + if (!dict.ContainsKey(Name.NAME)) + { + const string MSG = "Missing 'Name' connector attribute info."; + throw new ArgumentException(MSG); + } + } + + public ICollection ConnectorAttributeInfos + { + get + { + return this._info; + } + } + + public String ObjectType + { + get + { + return this._type; + } + } + + /// + /// Determines if the 'name' matches this . + /// + /// case-insensitive string representation of the ObjectClassInfo's + /// type. + /// + /// true if the case insensitive type is equal to + /// that of the one in this . + public bool Is(String name) + { + return NameUtil.NamesEqual(_type, name); + } + + public bool IsContainer + { + get + { + return this._isContainer; + } + } + + public override int GetHashCode() + { + return NameUtil.GetNameHashCode(_type); + } + + public override bool Equals(Object obj) + { + // test identity + if (this == obj) + { + return true; + } + + // test for null.. + if (obj == null) + { + return false; + } + + if (!obj.GetType().Equals(this.GetType())) + { + return false; + } + + ObjectClassInfo other = obj as ObjectClassInfo; + + if (!Is(other.ObjectType)) + { + return false; + } + + if (!CollectionUtil.Equals(ConnectorAttributeInfos, + other.ConnectorAttributeInfos)) + { + return false; + } + + if (_isContainer != other._isContainer) + { + return false; + } + + return true; + } + + public override string ToString() + { + return SerializerUtil.SerializeXmlObject(this, false); + } + } + #endregion + + #region ObjectClassInfoBuilder + /// + /// Used to help facilitate the building of objects. + /// + public sealed class ObjectClassInfoBuilder + { + private bool _isContainer; + private IDictionary _info; + + public ObjectClassInfoBuilder() + { + _info = new Dictionary(); + ObjectType = ObjectClass.ACCOUNT_NAME; + } + + public string ObjectType { get; set; } + + /// + /// Add each object to the . + /// + public ObjectClassInfoBuilder AddAttributeInfo(ConnectorAttributeInfo info) + { + if (_info.ContainsKey(info.Name)) + { + const string MSG = "ConnectorAttributeInfo of name ''{0}'' already exists!"; + throw new ArgumentException(String.Format(MSG, info.Name)); + } + _info[info.Name] = info; + return this; + } + + public ObjectClassInfoBuilder AddAllAttributeInfo(ICollection info) + { + foreach (ConnectorAttributeInfo cainfo in info) + { + AddAttributeInfo(cainfo); + } + return this; + } + + public bool IsContainer + { + get + { + return _isContainer; + } + set + { + _isContainer = value; + } + } + + public ObjectClassInfo Build() + { + // determine if name is missing and add it by default + if (!_info.ContainsKey(Name.NAME)) + { + _info[Name.NAME] = Name.INFO; + } + return new ObjectClassInfo(ObjectType, _info.Values, _isContainer); + } + } + #endregion + + #region OperationalAttributeInfos + /// + /// for each operational attribute. + /// + public static class OperationalAttributeInfos + { + /// + /// Gets/sets the enable status of an object. + /// + public static readonly ConnectorAttributeInfo ENABLE = + ConnectorAttributeInfoBuilder.Build( + OperationalAttributes.ENABLE_NAME, typeof(bool)); + + /// + /// Gets/sets the enable date for an object. + /// + public static readonly ConnectorAttributeInfo ENABLE_DATE = + ConnectorAttributeInfoBuilder.Build( + OperationalAttributes.ENABLE_DATE_NAME, typeof(long)); + + /// + /// Gets/sets the disable date for an object. + /// + public static readonly ConnectorAttributeInfo DISABLE_DATE = + ConnectorAttributeInfoBuilder.Build( + OperationalAttributes.DISABLE_DATE_NAME, typeof(long)); + + /// + /// Gets/sets the lock out attribute for an object. + /// + public static readonly ConnectorAttributeInfo LOCK_OUT = + ConnectorAttributeInfoBuilder.Build( + OperationalAttributes.LOCK_OUT_NAME, typeof(bool)); + + /// + /// Gets/sets the password expiration date for an object. + /// + public static readonly ConnectorAttributeInfo PASSWORD_EXPIRATION_DATE = + ConnectorAttributeInfoBuilder.Build( + OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME, typeof(long)); + + /// + /// Normally this is a write-only attribute. + /// + /// + /// Sets the password for an object. + /// + public static readonly ConnectorAttributeInfo PASSWORD = + ConnectorAttributeInfoBuilder.Build( + OperationalAttributes.PASSWORD_NAME, typeof(GuardedString), + ConnectorAttributeInfo.Flags.NOT_READABLE | + ConnectorAttributeInfo.Flags.NOT_RETURNED_BY_DEFAULT); + + /// + /// Used in conjunction with password to do an account level password change. + /// + /// + /// This is for a non-administrator change of the password and therefore + /// requires the current password. + /// + public static readonly ConnectorAttributeInfo CURRENT_PASSWORD = + ConnectorAttributeInfoBuilder.Build( + OperationalAttributes.CURRENT_PASSWORD_NAME, typeof(GuardedString), + ConnectorAttributeInfo.Flags.NOT_READABLE | + ConnectorAttributeInfo.Flags.NOT_RETURNED_BY_DEFAULT); + + /// + /// Used to determine if a password is expired or to expire a password. + /// + public static readonly ConnectorAttributeInfo PASSWORD_EXPIRED = + ConnectorAttributeInfoBuilder.Build( + OperationalAttributes.PASSWORD_EXPIRED_NAME, typeof(bool)); + + } + #endregion + + #region OperationalAttributes + /// + /// Operational attributes have special meaning and cannot be represented by pure + /// operations. + /// + /// + /// For instance some administrators would like to create an account + /// in the disabled state. The do not want this to be a two operation process + /// since this can leave the door open to abuse. Therefore special attributes + /// that can perform operations were introduced. The + /// attribute could be added to the set of + /// attribute sent to a Connector for the operation. To tell the + /// to create the account with it in the disabled state whether + /// the target resource itself has an attribute or an additional method must be + /// called. + /// + public static class OperationalAttributes + { + /// + /// Gets/sets the enable status of an object. + /// + public static readonly string ENABLE_NAME = ConnectorAttributeUtil.CreateSpecialName("ENABLE"); + /// + /// Gets/sets the enable date for an object. + /// + public static readonly string ENABLE_DATE_NAME = ConnectorAttributeUtil.CreateSpecialName("ENABLE_DATE"); + /// + /// Gets/sets the disable date for an object. + /// + public static readonly string DISABLE_DATE_NAME = ConnectorAttributeUtil.CreateSpecialName("DISABLE_DATE"); + /// + /// Gets/sets the lock out attribute for an object. + /// + public static readonly string LOCK_OUT_NAME = ConnectorAttributeUtil.CreateSpecialName("LOCK_OUT"); + /// + /// Gets/sets the password expiration date for an object. + /// + public static readonly string PASSWORD_EXPIRATION_DATE_NAME = ConnectorAttributeUtil.CreateSpecialName("PASSWORD_EXPIRATION_DATE"); + /// + /// Gets/sets the password expired for an object. + /// + public static readonly string PASSWORD_EXPIRED_NAME = ConnectorAttributeUtil.CreateSpecialName("PASSWORD_EXPIRED"); + /// + /// Normally this is a write-only attribute. + /// + /// + /// Sets the password for an object. + /// + public static readonly string PASSWORD_NAME = ConnectorAttributeUtil.CreateSpecialName("PASSWORD"); + /// + /// Used in conjunction with password to do an account level password change. + /// + /// + /// This is for a non-administrator change of the password and therefore + /// requires the current password. + /// + public static readonly string CURRENT_PASSWORD_NAME = ConnectorAttributeUtil.CreateSpecialName("CURRENT_PASSWORD"); + + // ======================================================================= + // Helper Methods.. + // ======================================================================= + public static readonly ICollection OPERATIONAL_ATTRIBUTE_NAMES = + CollectionUtil.NewReadOnlySet( + LOCK_OUT_NAME, + ENABLE_NAME, + ENABLE_DATE_NAME, + DISABLE_DATE_NAME, + PASSWORD_EXPIRATION_DATE_NAME, + PASSWORD_NAME, + CURRENT_PASSWORD_NAME, + PASSWORD_EXPIRED_NAME + ); + + public static ICollection GetOperationalAttributeNames() + { + return CollectionUtil.NewReadOnlySet(OPERATIONAL_ATTRIBUTE_NAMES); + } + public static bool IsOperationalAttribute(ConnectorAttribute attr) + { + string name = (attr != null) ? attr.Name : null; + return OPERATIONAL_ATTRIBUTE_NAMES.Contains(name); + } + } + #endregion + + #region PredefinedAttributes + /// + /// List of well known or pre-defined attributes. + /// + /// + /// Common attributes that most + /// resources have that are not operational in nature. + /// + public static class PredefinedAttributes + { + /// + /// Attribute that should hold a reasonable value to + /// display for the value of an object. + /// + /// + /// If this is not present, then the + /// application will have to use the NAME to show the value. + /// + public static readonly String SHORT_NAME = ConnectorAttributeUtil.CreateSpecialName("SHORT_NAME"); + + /// + /// Attribute that should hold the value of the object's description, + /// if one is available. + /// + public static readonly String DESCRIPTION = ConnectorAttributeUtil.CreateSpecialName("DESCRIPTION"); + + /// + /// Read-only attribute that shows the last date/time the password was + /// changed. + /// + public static readonly string LAST_PASSWORD_CHANGE_DATE_NAME = ConnectorAttributeUtil.CreateSpecialName("LAST_PASSWORD_CHANGE_DATE"); + + /// + /// Common password policy attribute where the password must be changed every + /// so often. + /// + /// + /// The value for this attribute is milliseconds since its the + /// lowest common denominator. + /// + public static readonly string PASSWORD_CHANGE_INTERVAL_NAME = ConnectorAttributeUtil.CreateSpecialName("PASSWORD_CHANGE_INTERVAL"); + + /// + /// Last login date for an account. + /// + /// + /// This is usually used to determine inactivity. + /// + public static readonly string LAST_LOGIN_DATE_NAME = ConnectorAttributeUtil.CreateSpecialName("LAST_LOGIN_DATE"); + + /// + /// Groups an account object belongs to. + /// + public static readonly string GROUPS_NAME = ConnectorAttributeUtil.CreateSpecialName("GROUPS"); + } + #endregion + + #region PredefinedAttributeInfos + public static class PredefinedAttributeInfos + { + /// + /// Attribute that should hold a reasonable value to + /// display for the value of an object. + /// + /// + /// If this is not present, then the + /// application will have to use the NAME to show the value. + /// + public static readonly ConnectorAttributeInfo SHORT_NAME = + ConnectorAttributeInfoBuilder.Build(PredefinedAttributes.SHORT_NAME); + + /// + /// Attribute that should hold the value of the object's description, + /// if one is available. + /// + public static readonly ConnectorAttributeInfo DESCRIPTION = + ConnectorAttributeInfoBuilder.Build(PredefinedAttributes.DESCRIPTION); + /// + /// Read-only attribute that shows the last date/time the password was + /// changed. + /// + public static readonly ConnectorAttributeInfo LAST_PASSWORD_CHANGE_DATE = + ConnectorAttributeInfoBuilder.Build( + PredefinedAttributes.LAST_PASSWORD_CHANGE_DATE_NAME, + typeof(long), + ConnectorAttributeInfo.Flags.NOT_CREATABLE | + ConnectorAttributeInfo.Flags.NOT_UPDATEABLE); + + /// + /// Common password policy attribute where the password must be changed every + /// so often. + /// + /// + /// The value for this attribute is milliseconds since its the + /// lowest common denominator. + /// + public static readonly ConnectorAttributeInfo PASSWORD_CHANGE_INTERVAL = + ConnectorAttributeInfoBuilder.Build( + PredefinedAttributes.PASSWORD_CHANGE_INTERVAL_NAME, typeof(long)); + + /// + /// Last login date for an account. + /// + /// + /// This is usually used to determine + /// inactivity. + /// + public static readonly ConnectorAttributeInfo LAST_LOGIN_DATE = + ConnectorAttributeInfoBuilder.Build( + PredefinedAttributes.LAST_LOGIN_DATE_NAME, + typeof(long), + ConnectorAttributeInfo.Flags.NOT_CREATABLE | + ConnectorAttributeInfo.Flags.NOT_UPDATEABLE); + + /// + /// Groups that an account or person belong to. + /// + /// + /// The Attribute values are the + /// UID value of each group that an account has membership in. + /// + public static readonly ConnectorAttributeInfo GROUPS = + ConnectorAttributeInfoBuilder.Build(PredefinedAttributes.GROUPS_NAME, + typeof(String), + ConnectorAttributeInfo.Flags.MULTIVALUED | + ConnectorAttributeInfo.Flags.NOT_RETURNED_BY_DEFAULT); + } + #endregion + + #region OperationOptions + /// + /// Arbitrary options to be passed into various operations. + /// + /// + /// This serves + /// as a catch-all for extra options. + /// + public sealed class OperationOptions + { + /// + /// An option to use with that specified the scope + /// under which to perform the search. + /// + /// + /// To be used in conjunction with + /// . Must be one of the following values + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public const String OP_SCOPE = "SCOPE"; + public const String SCOPE_OBJECT = "object"; + public const String SCOPE_ONE_LEVEL = "onelevel"; + public const String SCOPE_SUBTREE = "subtree"; + + /// + /// An option to use with that specified the container + /// under which to perform the search. + /// + /// + /// Must be of type . + /// Should be implemented for those object classes whose + /// returns true. + /// + public const String OP_CONTAINER = "CONTAINER"; + + /// + /// An option to use with and possibly others + /// that specifies an account under which to execute the script/operation. + /// + /// + /// The specified account will appear to have performed any action that the + /// script/operation performs. + /// + /// Check the javadoc for a particular connector to see whether that + /// connector supports this option. + /// + /// + public static readonly string OP_RUN_AS_USER = "RUN_AS_USER"; + + /// + /// An option to use with and possibly others + /// that specifies a password under which to execute the script/operation. + /// + public static readonly string OP_RUN_WITH_PASSWORD = "RUN_WITH_PASSWORD"; + + /// + /// Determines the attributes to retrieve during and + /// . + /// + /// This option overrides the default behavior, which is for the connector to + /// return exactly the set of attributes that are identified as + /// in the + /// schema for that connector. + /// + /// + /// This option allows a client application to request additional + /// attributes that would not otherwise not be returned (generally + /// because such attributes are more expensive for a connector to fetch and + /// to format) and/or to request only a subset of the attributes that + /// would normally be returned. + /// + /// + public static readonly string OP_ATTRIBUTES_TO_GET = "ATTRS_TO_GET"; + + /// + /// An option to use with that specifies an opaque cookie + /// which is used by the connector to track its position in the set of query + /// results. + /// + public static readonly string OP_PAGED_RESULTS_COOKIE = "PAGED_RESULTS_COOKIE"; + + /// + /// An option to use with that specifies the policy used for calculating + /// the total number of paged results. + /// + public const string OP_TOTAL_PAGED_RESULTS_POLICY = "TOTAL_PAGED_RESULTS_POLICY"; + + /// + /// An option to use with that specifies the index within + /// the result set of the first result which should be returned. + /// + public static readonly string OP_PAGED_RESULTS_OFFSET = "PAGED_RESULTS_OFFSET"; + + /// + /// An option to use with that specifies the requested + /// page results page size. + /// + public static readonly string OP_PAGE_SIZE = "PAGE_SIZE"; + + /// + /// An option to use with that specifies the sort keys + /// which should be used for ordering the returned by + /// search request. + /// + public static readonly string OP_SORT_KEYS = "SORT_KEYS"; + + private readonly IDictionary _operationOptions; + + /// + /// Public only for serialization; please use . + /// + /// The options. + public OperationOptions(IDictionary operationOptions) + { + foreach (Object val in operationOptions.Values) + { + FrameworkUtil.CheckOperationOptionValue(val); + } + //clone options to do a deep copy in case anything + //is an array + IDictionary operationOptionsClone = (IDictionary)SerializerUtil.CloneObject(operationOptions); + _operationOptions = CollectionUtil.NewReadOnlyDictionary(operationOptionsClone); + } + + /// + /// Returns a map of options. + /// + /// + /// Each value in the map + /// must be of a type that the framework can serialize. + /// See for a list of supported types. + /// + /// A map of options. + public IDictionary Options + { + get + { + return _operationOptions; + } + } + + /// + /// Convenience method that returns . + /// + /// The value for . + public String Scope + { + get + { + return (String)CollectionUtil.GetValue(_operationOptions, OP_SCOPE, null); + } + } + + /// + /// Convenience method that returns . + /// + /// The value for . + public QualifiedUid getContainer + { + get + { + return (QualifiedUid)CollectionUtil.GetValue(_operationOptions, OP_CONTAINER, null); + } + } + + /// + /// Get the string array of attribute names to return in the object. + /// + public string[] AttributesToGet + { + get + { + return (string[])CollectionUtil.GetValue( + _operationOptions, OP_ATTRIBUTES_TO_GET, null); + } + } + + /// + /// Get the account to run the operation as.. + /// + public string RunAsUser + { + get + { + return (string)CollectionUtil.GetValue( + _operationOptions, OP_RUN_AS_USER, null); + } + } + + /// + /// Get the password to run the operation as.. + /// + public GuardedString RunWithPassword + { + get + { + return (GuardedString)CollectionUtil.GetValue( + _operationOptions, OP_RUN_WITH_PASSWORD, null); + } + } + + /// + /// Returns the opaque cookie which is used by the Connector to track its + /// position in the set of query results. Paged results will be enabled if + /// and only if the page size is non-zero. + /// + /// The cookie must be {@code null} in the initial search request sent by the + /// client. For subsequent search requests the client must include the cookie + /// returned with the previous search result, until the resource provider + /// returns a {@code null} cookie indicating that the final page of results + /// has been returned. + /// + /// + /// + /// The opaque cookie which is used by the Connector to track its + /// position in the set of search results, or {@code null} if paged + /// results are not requested (when the page size is 0), or if the + /// first page of results is being requested (when the page size is + /// non-zero). + /// + /// + /// Since 1.4 + public string PagedResultsCookie + { + get + { + return (string)CollectionUtil.GetValue( + _operationOptions, OP_PAGED_RESULTS_COOKIE, null); + } + } + + /// + /// Returns the used to calculate + /// . + /// + /// The count policy. + /// + /// Since 1.5 + public SearchResult.CountPolicy TotalPagedResultsPolicy + { + get + { + return (SearchResult.CountPolicy)CollectionUtil.GetValue( + _operationOptions, OP_TOTAL_PAGED_RESULTS_POLICY, SearchResult.CountPolicy.NONE); + } + } + + /// + /// Returns the index within the result set of the first result which should + /// be returned. Paged results will be enabled if and only if the page size + /// is non-zero. If the parameter is not present or a value less than 1 is + /// specified then the page following the previous page returned will be + /// returned. A value equal to or greater than 1 indicates that a specific + /// page should be returned starting from the position specified. + /// + /// The index within the result set of the first result which should + /// be returned. + /// + /// + /// Since 1.4 + public int? PagedResultsOffset + { + get + { + return (int?)CollectionUtil.GetValue( + _operationOptions, OP_PAGED_RESULTS_OFFSET, null); + } + } + + /// + /// Returns the requested page results page size or {@code 0} if paged + /// results are not required. For all paged result requests other than the + /// initial request, a cookie should be provided with the search request. See + /// for more information. + /// + /// The requested page results page size or {@code 0} if paged + /// results are not required. + /// + /// + /// Since 1.4 + public int? PageSize + { + get + { + return (int?)CollectionUtil.GetValue( + _operationOptions, OP_PAGE_SIZE, null); + } + } + + /// + /// Returns the sort keys which should be used for ordering the + /// s returned by this search request. + /// + /// The sort keys which should be used for ordering the + /// s returned by this search request (never + /// {@code null}). + /// Since 1.4 + public SortKey[] SortKeys + { + get + { + return (SortKey[])CollectionUtil.GetValue( + _operationOptions, OP_SORT_KEYS, null); + } + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("OperationOptions: ").Append(Options); + return bld.ToString(); + } + } + #endregion + + #region OperationOptionsBuilder + /// + /// Builder for . + /// + public sealed class OperationOptionsBuilder + { + private readonly IDictionary _options; + + /// + /// Create a builder with an empty set of options. + /// + /// new builder + /// Since 1.5 + public static OperationOptionsBuilder Create() + { + return new OperationOptionsBuilder(); + } + + /// + /// Create a builder with an empty set of options. + /// + public OperationOptionsBuilder() + { + _options = new Dictionary(); + } + + /// + /// Create a builder from an existing set of options. + /// + /// The existing set of options. Must not be null. + public OperationOptionsBuilder(OperationOptions options) + { + Assertions.NullCheck(options, "options"); + // clone options to do a deep copy in case anything + // is an array + IDictionary operationOptionsClone = (IDictionary)SerializerUtil + .CloneObject(options.Options); + _options = CollectionUtil.NewDictionary(operationOptionsClone); + } + + /// + /// Sets a given option and a value for that option. + /// + /// The name of the option + /// The value of the option. Must be one of the types that + /// we can serialize. + /// See for a list of supported types. + public OperationOptionsBuilder SetOption(String name, Object value) + { + if (name == null) + { + throw new ArgumentException("Argument 'value' cannot be null."); + } + //don't validate value here - we do that implicitly when + //we clone in the constructor of OperationOptions + _options[name] = value; + return this; + } + + /// + /// Returns a mutable reference of the options map. + /// + /// A mutable reference of the options map. + public IDictionary Options + { + get + { + //might as well be mutable since it's the builder and + //we don't want to deep copy anyway + return _options; + } + } + + /// + /// Creates the OperationOptions. + /// + /// The newly-created OperationOptions + public OperationOptions Build() + { + return new OperationOptions(_options); + } + + /// + /// Sets the option. + /// + /// list of names. + public string[] AttributesToGet + { + set + { + Assertions.NullCheck(value, "AttributesToGet"); + // don't validate value here - we do that in + // the constructor of OperationOptions - that's + // really the only place we can truly enforce this + _options[OperationOptions.OP_ATTRIBUTES_TO_GET] = value; + } + } + + /// + /// Set the run with password option. + /// + public GuardedString RunWithPassword + { + set + { + Assertions.NullCheck(value, "RunWithPassword"); + _options[OperationOptions.OP_RUN_WITH_PASSWORD] = value; + } + } + + /// + /// Set the run as user option. + /// + public string RunAsUser + { + set + { + Assertions.NullCheck(value, "RunAsUser"); + _options[OperationOptions.OP_RUN_AS_USER] = value; + } + } + /// + /// Convenience method to set + /// + /// The scope. May not be null. + /// A this reference to allow chaining + public string Scope + { + set + { + Assertions.NullCheck(value, "Scope"); + _options[OperationOptions.OP_SCOPE] = value; + } + } + + /// + /// Convenience method to set + /// + /// The container. May not be null. + /// A this reference to allow chaining + public QualifiedUid Container + { + set + { + Assertions.NullCheck(value, "Container"); + _options[OperationOptions.OP_CONTAINER] = value; + } + } + + + /// + /// Convenience method to set + /// + /// + /// + /// The pagedResultsCookie. May not be null. + /// A this reference to allow chaining + /// Since 1.4 + public string PagedResultsCookie + { + set + { + Assertions.NullCheck(value, "pagedResultsCookie"); + _options[OperationOptions.OP_PAGED_RESULTS_COOKIE] = value; + } + } + + /// + /// Sets the policy for calculating the total number of paged results. If no + /// count policy is supplied or paged results are not requested a default of + /// will be used. This will result in no count being + /// performed and no overhead incurred. + /// + /// Since 1.5 + public SearchResult.CountPolicy TotalPagedResultsPolicy + { + set + { + Assertions.NullCheck(value, "totalPagedResultsPolicy"); + _options[OperationOptions.OP_TOTAL_PAGED_RESULTS_POLICY] = value; + } + } + + /// + /// Convenience method to set + /// + /// + /// + /// The pagedResultsOffset. May not be null. + /// A this reference to allow chaining + /// Since 1.4 + public int? PagedResultsOffset + { + set + { + Assertions.NullCheck(value, "pagedResultsOffset"); + _options[OperationOptions.OP_PAGED_RESULTS_OFFSET] = value; + } + } + + /// + /// Convenience method to set + /// + /// + /// The pageSize. May not be null. + /// A this reference to allow chaining + /// Since 1.4 + public int? PageSize + { + set + { + Assertions.NullCheck(value, "pageSize"); + _options[OperationOptions.OP_PAGE_SIZE] = value; + } + } + + /// + /// Convenience method to set + /// + /// + /// The sort keys. May not be null. + /// A this reference to allow chaining + /// Since 1.4 + public IList SortKeys + { + set + { + Assertions.NullCheck(value, "sortKeys"); + SortKey[] array = new SortKey[((IList)value).Count]; + ((IList)value).CopyTo(array, 0); + _options[OperationOptions.OP_SORT_KEYS] = array; + } + } + + /// + /// Convenience method to set + /// + /// + /// The sort keys. May not be null. + /// A this reference to allow chaining + /// Since 1.4 + public OperationOptionsBuilder SetSortKeys(params SortKey[] sortKeys) + { + + Assertions.NullCheck(sortKeys, "sortKeys"); + _options[OperationOptions.OP_SORT_KEYS] = sortKeys; + return this; + } + + } + #endregion + + #region OperationOptionInfo + public sealed class OperationOptionInfo + { + private String _name; + private Type _type; + + public OperationOptionInfo(String name, + Type type) + { + Assertions.NullCheck(name, "name"); + Assertions.NullCheck(type, "type"); + FrameworkUtil.CheckOperationOptionType(type); + _name = name; + _type = type; + } + + public String Name + { + get + { + return _name; + } + } + + public Type OptionType + { + get + { + return _type; + } + } + + public override bool Equals(Object o) + { + if (o is OperationOptionInfo) + { + OperationOptionInfo other = + (OperationOptionInfo)o; + if (!_name.Equals(other._name)) + { + return false; + } + if (!_type.Equals(other._type)) + { + return false; + } + return true; + } + return false; + } + + public override int GetHashCode() + { + return _name.GetHashCode(); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("OperationOptionInfo("); + bld.Append(_name); + bld.Append(_type.ToString()); + bld.Append(')'); + return bld.ToString(); + } + + } + #endregion + + #region OperationOptionInfoBuilder + public sealed class OperationOptionInfoBuilder + { + private String _name; + private Type _type; + + public OperationOptionInfoBuilder() + { + } + + public OperationOptionInfoBuilder(String name, + Type type) + { + _name = name; + _type = type; + } + + public String Name + { + get + { + return _name; + } + set + { + _name = value; + } + } + + public Type OptionType + { + get + { + return _type; + } + set + { + _type = value; + } + } + + public OperationOptionInfo Build() + { + return new OperationOptionInfo(_name, _type); + } + + public static OperationOptionInfo Build(String name, Type type) + { + return new OperationOptionInfoBuilder(name, type).Build(); + } + + public static OperationOptionInfo Build(String name) + { + return Build(name, typeof(string)); + } + + public static OperationOptionInfo BuildAttributesToGet() + { + return Build(OperationOptions.OP_ATTRIBUTES_TO_GET, typeof(string[])); + } + + public static OperationOptionInfo BuildRunWithPassword() + { + return Build(OperationOptions.OP_RUN_WITH_PASSWORD, typeof(GuardedString)); + } + + public static OperationOptionInfo BuildRunAsUser() + { + return Build(OperationOptions.OP_RUN_AS_USER); + } + public static OperationOptionInfo BuildScope() + { + return Build(OperationOptions.OP_SCOPE); + } + + public static OperationOptionInfo BuildContainer() + { + return Build(OperationOptions.OP_CONTAINER, typeof(QualifiedUid)); + } + + public static OperationOptionInfo BuildPagedResultsCookie() + { + return Build(OperationOptions.OP_PAGED_RESULTS_COOKIE); + } + + public static OperationOptionInfo BuildPagedResultsOffset() + { + return Build(OperationOptions.OP_PAGED_RESULTS_OFFSET, typeof(int?)); + } + + public static OperationOptionInfo BuildPageSize() + { + return Build(OperationOptions.OP_PAGE_SIZE, typeof(int?)); + } + + public static OperationOptionInfo BuildSortKeys() + { + return Build(OperationOptions.OP_SORT_KEYS, typeof(SortKey)); + } + } + #endregion + + #region QualifiedUid + /// + /// A fully-qualified uid. + /// + /// + /// That is, a pair of and + /// . + /// + public sealed class QualifiedUid + { + private readonly ObjectClass _objectClass; + private readonly Uid _uid; + + /// + /// Create a QualifiedUid. + /// + /// The object class. May not be null. + /// The uid. May not be null. + public QualifiedUid(ObjectClass objectClass, + Uid uid) + { + Assertions.NullCheck(objectClass, "objectClass"); + Assertions.NullCheck(uid, "uid"); + _objectClass = objectClass; + _uid = uid; + } + + /// + /// Returns the object class. + /// + /// The object class. + public ObjectClass ObjectClass + { + get + { + return _objectClass; + } + } + + /// + /// Returns the uid. + /// + /// The uid. + public Uid Uid + { + get + { + return _uid; + } + } + + /// + /// Returns true if o is a QualifiedUid and the object class and uid match. + /// + public override bool Equals(Object o) + { + if (o is QualifiedUid) + { + QualifiedUid other = (QualifiedUid)o; + return (_objectClass.Equals(other._objectClass) && + _uid.Equals(other._uid)); + } + return false; + } + + /// + /// Returns a hash code based on uid + /// + public override int GetHashCode() + { + return _uid.GetHashCode(); + } + + /// + /// Returns a string representation acceptible for debugging. + /// + public override String ToString() + { + return SerializerUtil.SerializeXmlObject(this, false); + } + + } + #endregion + + #region ResultsHandler + public class ResultsHandler + { + /// + /// Invoked each time a matching is returned from a + /// query request. + /// + /// + /// The matching ConnectorObject. + /// {@code true} if this handler should continue to be notified of + /// any remaining matching ConnectorObjects, or {@code false} if the + /// remaining ConnectorObjects should be skipped for some reason + /// (e.g. a client side size limit has been reached or the failed to + /// handle the last item). If returns {@code false} the last items + /// should be considers unhandled and in next page request it should + /// be the first item. + /// + /// + /// the implementor should throw a that + /// wraps any native exception (or that describes any other + /// problem during execution) that is serious enough to stop the + /// iteration. + public Func Handle; + } + #endregion + + #region Schema + /// + /// Determines the objects supported by a + /// . + /// + /// + /// The object is used to represent the basic objects that a + /// connector supports. This does not prevent a connector from supporting more. + /// Rather, this is informational for the caller of the connector to understand + /// a minimum support level. + /// The schema defines 4 primary data structures + /// + /// + /// Declared ObjectClasses (). + /// + /// + /// + /// Declared OperationOptionInfo (). + /// + /// + /// + /// Supported ObjectClasses by operation (). + /// + /// + /// + /// Supported OperationOptionInfo by operation(). + /// + /// + /// + /// TODO: add more to describe and what is expected from this call and how it is + /// used.. based on OperationalAttribute etc.. + /// + public sealed class Schema + { + private readonly ICollection _declaredObjectClasses; + private readonly ICollection _declaredOperationOptions; + private readonly IDictionary, ICollection> + _supportedObjectClassesByOperation; + private readonly IDictionary, ICollection> + _supportedOptionsByOperation; + + /// + /// Public only for serialization; please use + /// SchemaBuilder instead. + /// + /// + /// + public Schema(ICollection info, + ICollection options, + IDictionary, ICollection> supportedObjectClassesByOperation, + IDictionary, ICollection> supportedOptionsByOperation) + { + _declaredObjectClasses = CollectionUtil.NewReadOnlySet(info); + _declaredOperationOptions = CollectionUtil.NewReadOnlySet(options); + + //make read-only + { + IDictionary, ICollection> temp = + new Dictionary, ICollection>(); + foreach (KeyValuePair, ICollection> entry in + supportedObjectClassesByOperation) + { + SafeType op = + entry.Key; + ICollection resolvedClasses = + CollectionUtil.NewReadOnlySet(entry.Value); + temp[op] = resolvedClasses; + } + _supportedObjectClassesByOperation = CollectionUtil.AsReadOnlyDictionary(temp); + } + //make read-only + { + IDictionary, ICollection> temp = + new Dictionary, ICollection>(); + foreach (KeyValuePair, ICollection> entry in + supportedOptionsByOperation) + { + SafeType op = + entry.Key; + ICollection resolvedClasses = + CollectionUtil.NewReadOnlySet(entry.Value); + temp[op] = resolvedClasses; + } + _supportedOptionsByOperation = CollectionUtil.AsReadOnlyDictionary(temp); + } + } + + /// + /// Returns the set of object classes that are defined in the schema, regardless + /// of which operations support them. + /// + public ICollection ObjectClassInfo + { + get + { + return _declaredObjectClasses; + } + } + + /// + /// Returns the ObjectClassInfo for the given type. + /// + /// The type to find. + /// the ObjectClassInfo for the given type or null if not found. + public ObjectClassInfo FindObjectClassInfo(String type) + { + foreach (ObjectClassInfo info in _declaredObjectClasses) + { + if (info.Is(type)) + { + return info; + } + } + return null; + } + + /// + /// Returns the set of operation options that are defined in the schema, regardless + /// of which operations support them. + /// + /// The options defined in this schema. + public ICollection OperationOptionInfo + { + get + { + return _declaredOperationOptions; + } + } + + /// + /// Returns the OperationOptionInfo for the given name. + /// + /// The name to find. + /// the OperationOptionInfo for the given name or null if not found. + public OperationOptionInfo FindOperationOptionInfo(String name) + { + Assertions.NullCheck(name, "name"); + foreach (OperationOptionInfo info in _declaredOperationOptions) + { + if (info.Name.Equals(name)) + { + return info; + } + } + return null; + } + + /// + /// Returns the supported object classes for the given operation. + /// + /// The operation. + /// the supported object classes for the given operation. + public ICollection GetSupportedObjectClassesByOperation(SafeType apiop) + { + ICollection rv = + CollectionUtil.GetValue(_supportedObjectClassesByOperation, apiop, null); + if (rv == null) + { + ICollection empty = + CollectionUtil.NewReadOnlySet(); + + return empty; + } + else + { + return rv; + } + } + + /// + /// Returns the supported options for the given operation. + /// + /// The operation. + /// the supported options for the given operation. + public ICollection GetSupportedOptionsByOperation(SafeType apiop) + { + ICollection rv = + CollectionUtil.GetValue(_supportedOptionsByOperation, apiop, null); + if (rv == null) + { + ICollection empty = + CollectionUtil.NewReadOnlySet(); + return empty; + } + else + { + return rv; + } + } + + /// + /// Returns the set of object classes that apply to a particular operation. + /// + /// the set of object classes that apply to a particular operation. + public IDictionary, ICollection> SupportedObjectClassesByOperation + { + get + { + return _supportedObjectClassesByOperation; + } + } + /// + /// Returns the set of operation options that apply to a particular operation. + /// + /// the set of operation options that apply to a particular operation. + public IDictionary, ICollection> SupportedOptionsByOperation + { + get + { + return _supportedOptionsByOperation; + } + } + + + + public override int GetHashCode() + { + return CollectionUtil.GetHashCode(_declaredObjectClasses); + } + + public override bool Equals(object o) + { + Schema other = o as Schema; + if (other != null) + { + if (!CollectionUtil.Equals(ObjectClassInfo, other.ObjectClassInfo)) + { + return false; + } + if (!CollectionUtil.Equals(OperationOptionInfo, other.OperationOptionInfo)) + { + return false; + } + if (!CollectionUtil.Equals(_supportedObjectClassesByOperation, + other._supportedObjectClassesByOperation)) + { + return false; + } + if (!CollectionUtil.Equals(_supportedOptionsByOperation, + other._supportedOptionsByOperation)) + { + return false; + } + return true; + } + return false; + } + + public override string ToString() + { + return SerializerUtil.SerializeXmlObject(this, false); + } + } + #endregion + + #region SchemaBuilder + /// + /// Simple builder class to help facilitate creating a object. + /// + public sealed class SchemaBuilder + { + private readonly ICollection _declaredObjectClasses + = new HashSet(); + private readonly ICollection _declaredOperationOptions + = new HashSet(); + + private readonly IDictionary, ICollection> + _supportedObjectClassesByOperation = + new Dictionary, ICollection>(); + private readonly IDictionary, ICollection> + _supportedOptionsByOperation = + new Dictionary, ICollection>(); + + private readonly ICollection> _defaultSupportedOperations; + + /// + /// + public SchemaBuilder(SafeType connectorClass) + { + Assertions.NullCheck(connectorClass, "connectorClass"); + _defaultSupportedOperations = FrameworkUtil.GetDefaultSupportedOperations(connectorClass); + } + + private bool ObjectClassOperation(SafeType op) + { + if (typeof(AuthenticationApiOp) == op.RawType || + typeof(CreateApiOp) == op.RawType || + typeof(DeleteApiOp) == op.RawType || + typeof(GetApiOp) == op.RawType || + typeof(ResolveUsernameApiOp) == op.RawType || + typeof(SearchApiOp) == op.RawType || + typeof(SyncApiOp) == op.RawType || + typeof(UpdateApiOp) == op.RawType) + { + return true; + } + return false; + } + + private bool OperationOptionOperation(SafeType op) + { + if (typeof(AuthenticationApiOp) == op.RawType || + typeof(CreateApiOp) == op.RawType || + typeof(DeleteApiOp) == op.RawType || + typeof(GetApiOp) == op.RawType || + typeof(ResolveUsernameApiOp) == op.RawType || + typeof(ScriptOnConnectorApiOp) == op.RawType || + typeof(ScriptOnResourceApiOp) == op.RawType || + typeof(SearchApiOp) == op.RawType || + typeof(SyncApiOp) == op.RawType || + typeof(UpdateApiOp) == op.RawType) + { + return true; + } + return false; + } + + /// + /// Adds another ObjectClassInfo to the schema. + /// + /// + /// Also, adds this + /// to the set of supported classes for every operation defined by + /// the Connector. + /// + /// + /// If already defined + public void DefineObjectClass(ObjectClassInfo info) + { + Assertions.NullCheck(info, "info"); + if (_declaredObjectClasses.Contains(info)) + { + throw new InvalidOperationException("ObjectClass already defined: " + + info.ObjectType); + } + _declaredObjectClasses.Add(info); + foreach (SafeType op in _defaultSupportedOperations) + { + if (ObjectClassOperation(op)) + { + ICollection oclasses = + CollectionUtil.GetValue(_supportedObjectClassesByOperation, op, null); + if (oclasses == null) + { + oclasses = new HashSet(); + _supportedObjectClassesByOperation[op] = oclasses; + } + oclasses.Add(info); + } + } + } + /// + /// Adds another ObjectClassInfo to the schema. + /// + /// Also, adds this to the set of supported classes for every operation + /// defined by the Connector. + /// + /// + /// + /// The SPI operation which use supports this + /// objectClassInfo + /// + /// + /// If already defined + public void DefineObjectClass(ObjectClassInfo info, params SafeType[] operations) + { + if (operations.Length > 0) + { + Assertions.NullCheck(info, "objectClassInfo"); + if (_declaredObjectClasses.Contains(info)) + { + throw new InvalidOperationException("ObjectClass already defined: " + info.ObjectType); + } + _declaredObjectClasses.Add(info); + foreach (SafeType spi in operations) + { + if (typeof(SchemaOp) == spi.RawType || + typeof(ScriptOnConnectorOp) == spi.RawType || + typeof(ScriptOnResourceOp) == spi.RawType || + typeof(TestOp) == spi.RawType) + { + continue; + } + IEnumerable> apiOperations = FrameworkUtil.Spi2Apis(spi).Intersect(_defaultSupportedOperations); + foreach (SafeType op in apiOperations) + { + if (ObjectClassOperation(op)) + { + ICollection oclasses = + CollectionUtil.GetValue(_supportedObjectClassesByOperation, op, null); + if (oclasses == null) + { + oclasses = new HashSet(); + _supportedObjectClassesByOperation[op] = oclasses; + } + oclasses.Add(info); + } + } + } + } + else + { + DefineObjectClass(info); + } + } + /// + /// Adds another OperationOptionInfo to the schema. + /// + /// + /// Also, adds this + /// to the set of supported options for every operation defined by + /// the Connector. + /// + public void DefineOperationOption(OperationOptionInfo info) + { + Assertions.NullCheck(info, "info"); + if (_declaredOperationOptions.Contains(info)) + { + throw new InvalidOperationException("OperationOption already defined: " + + info.Name); + } + _declaredOperationOptions.Add(info); + foreach (SafeType op in _defaultSupportedOperations) + { + if (OperationOptionOperation(op)) + { + ICollection oclasses = + CollectionUtil.GetValue(_supportedOptionsByOperation, op, null); + if (oclasses == null) + { + oclasses = new HashSet(); + _supportedOptionsByOperation[op] = oclasses; + } + oclasses.Add(info); + } + } + } + /// + /// Adds another OperationOptionInfo to the schema. Also, adds this to the + /// set of supported options for operation defined. + /// + /// + /// + /// + /// + /// If already defined + public void DefineOperationOption(OperationOptionInfo info, params SafeType[] operations) + { + if (operations.Length > 0) + { + Assertions.NullCheck(info, "info"); + if (_declaredOperationOptions.Contains(info)) + { + throw new InvalidOperationException("OperationOption already defined: " + info.Name); + } + _declaredOperationOptions.Add(info); + foreach (SafeType spi in operations) + { + if (typeof(SchemaOp) == spi.RawType || + typeof(TestOp) == spi.RawType) + { + continue; + } + IEnumerable> apiOperations = FrameworkUtil.Spi2Apis(spi).Intersect(_defaultSupportedOperations); + foreach (SafeType op in apiOperations) + { + if (OperationOptionOperation(op)) + { + ICollection oclasses = + CollectionUtil.GetValue(_supportedOptionsByOperation, op, null); + if (oclasses == null) + { + oclasses = new HashSet(); + _supportedOptionsByOperation[op] = oclasses; + } + oclasses.Add(info); + } + } + } + } + else + { + DefineOperationOption(info); + } + } + + /// + /// Adds another ObjectClassInfo to the schema. + /// + /// + /// Also, adds this + /// to the set of supported classes for every operation defined by + /// the Connector. + /// + /// If already defined + public void DefineObjectClass(String type, ICollection attrInfo) + { + ObjectClassInfoBuilder bld = new ObjectClassInfoBuilder(); + bld.ObjectType = type; + bld.AddAllAttributeInfo(attrInfo); + ObjectClassInfo obj = bld.Build(); + DefineObjectClass(obj); + } + + /// + /// Adds another OperationOptionInfo to the schema. + /// + /// + /// Also, adds this + /// to the set of supported options for every operation defined by + /// the Connector. + /// + /// If already defined + public void DefineOperationOption(String optionName, Type type) + { + OperationOptionInfoBuilder bld = new OperationOptionInfoBuilder(); + bld.Name = (optionName); + bld.OptionType = (type); + OperationOptionInfo info = bld.Build(); + DefineOperationOption(info); + } + + /// + /// Adds the given ObjectClassInfo as a supported ObjectClass for + /// the given operation. + /// + /// The SPI operation + /// The ObjectClassInfo + /// If the given ObjectClassInfo was + /// not already defined using . + public void AddSupportedObjectClass(SafeType op, + ObjectClassInfo def) + { + Assertions.NullCheck(op, "op"); + Assertions.NullCheck(def, "def"); + IEnumerable> apis = + FrameworkUtil.Spi2Apis(op).Intersect(_defaultSupportedOperations); + if (!_declaredObjectClasses.Contains(def)) + { + throw new ArgumentException("ObjectClass " + def.ObjectType + + " not defined in schema."); + } + foreach (SafeType api in apis) + { + if (ObjectClassOperation(api)) + { + ICollection infos = + CollectionUtil.GetValue(_supportedObjectClassesByOperation, api, null); + if (infos == null) + { + throw new ArgumentException("Operation " + op + + " not implement by connector."); + } + if (infos.Contains(def)) + { + throw new ArgumentException("ObjectClass " + def.ObjectType + + " already supported for operation " + op); + } + infos.Add(def); + } + } + } + + /// + /// Removes the given ObjectClassInfo as a supported ObjectClass for + /// the given operation. + /// + /// The SPI operation + /// The ObjectClassInfo + /// If the given ObjectClassInfo was + /// not already defined using . + public void RemoveSupportedObjectClass(SafeType op, + ObjectClassInfo def) + { + Assertions.NullCheck(op, "op"); + Assertions.NullCheck(def, "def"); + ICollection> apis = + FrameworkUtil.Spi2Apis(op); + if (!_declaredObjectClasses.Contains(def)) + { + throw new ArgumentException("ObjectClass " + def.ObjectType + + " not defined in schema."); + } + foreach (SafeType api in apis) + { + if (ObjectClassOperation(api)) + { + if (_defaultSupportedOperations.Contains(api)) + { + ICollection infos = + CollectionUtil.GetValue(_supportedObjectClassesByOperation, api, null); + if (infos == null || !infos.Contains(def)) + { + throw new ArgumentException("ObjectClass " + def.ObjectType + + " already removed for operation " + op); + } + infos.Remove(def); + } + else + { + throw new ArgumentException("Operation " + op + + " not implement by connector."); + } + } + } + } + /// + /// Adds the given OperationOptionInfo as a supported option for + /// the given operation. + /// + /// The SPI operation + /// The OperationOptionInfo + /// If the given OperationOptionInfo was + /// not already defined using . + public void AddSupportedOperationOption(SafeType op, + OperationOptionInfo def) + { + Assertions.NullCheck(op, "op"); + Assertions.NullCheck(def, "def"); + IEnumerable> apis = + FrameworkUtil.Spi2Apis(op).Intersect(_defaultSupportedOperations); + if (!_declaredOperationOptions.Contains(def)) + { + throw new ArgumentException("OperationOption " + def.Name + + " not defined in schema."); + } + foreach (SafeType api in apis) + { + if (OperationOptionOperation(api)) + { + ICollection infos = + CollectionUtil.GetValue(_supportedOptionsByOperation, api, null); + if (infos == null) + { + throw new ArgumentException("Operation " + op + + " not implement by connector."); + } + if (infos.Contains(def)) + { + throw new ArgumentException("OperationOption " + def.Name + + " already supported for operation " + op); + } + infos.Add(def); + } + } + } + + /// + /// Removes the given OperationOptionInfo as a supported option for + /// the given operation. + /// + /// The SPI operation + /// The OperationOptionInfo + /// If the given OperationOptionInfo was + /// not already defined using . + public void RemoveSupportedOperationOption(SafeType op, + OperationOptionInfo def) + { + Assertions.NullCheck(op, "op"); + Assertions.NullCheck(def, "def"); + ICollection> apis = + FrameworkUtil.Spi2Apis(op); + if (!_declaredOperationOptions.Contains(def)) + { + throw new ArgumentException("OperationOption " + def.Name + + " not defined in schema."); + } + foreach (SafeType api in apis) + { + if (OperationOptionOperation(api)) + { + if (_defaultSupportedOperations.Contains(api)) + { + ICollection infos = + CollectionUtil.GetValue(_supportedOptionsByOperation, api, null); + if (infos == null || !infos.Contains(def)) + { + throw new ArgumentException("OperationOption " + def.Name + + " already removed for operation " + op); + } + infos.Remove(def); + } + else + { + throw new ArgumentException("Operation " + op + + " not implement by connector."); + } + } + } + } + + /// + /// Clears the operation-specific supported classes. + /// + /// + /// Normally, when + /// you add an ObjectClass, using , + /// it is added to all operations. You may then remove those that you need + /// using . You + /// may wish, as an alternative to clear everything out and instead add using + /// . + /// + public void ClearSupportedObjectClassesByOperation() + { + foreach (ICollection values in + _supportedObjectClassesByOperation.Values) + { + values.Clear(); + } + } + /// + /// Clears the operation-specific supported options. + /// + /// + /// Normally, when + /// you add an OperationOptionInfo, using , + /// it is added to all operations. You may then remove those that you need + /// using . You + /// may wish, as an alternative to clear everything out and instead add using + /// . + /// + public void ClearSupportedOptionsByOperation() + { + foreach (ICollection values in + _supportedOptionsByOperation.Values) + { + values.Clear(); + } + } + + /// + /// Builds the object based on the s + /// added so far. + /// + /// new Schema object based on the info provided. + public Schema Build() + { + if (_declaredObjectClasses.Count == 0) + { + String ERR = "Must be at least one ObjectClassInfo object!"; + throw new InvalidOperationException(ERR); + } + return new Schema(_declaredObjectClasses, + _declaredOperationOptions, + _supportedObjectClassesByOperation, + _supportedOptionsByOperation); + } + } + #endregion + + #region Script + /// + /// Represents a script in a scripting language. + /// + /// 1.1 + public sealed class Script + { + + private readonly string scriptLanguage; + private readonly string scriptText; + + internal Script(string scriptLanguage, string scriptText) + { + Assertions.BlankCheck(scriptLanguage, "scriptLanguage"); + Assertions.NullCheck(scriptText, "scriptText"); // Allow empty text. + this.scriptLanguage = scriptLanguage; + this.scriptText = scriptText; + } + + /// + /// Returns the language of this script. + /// + /// the script language; never null. + public string ScriptLanguage + { + get + { + return scriptLanguage; + } + } + + /// + /// Returns the text of this script. + /// + /// the script text; never null. + public string ScriptText + { + get + { + return scriptText; + } + } + + public override int GetHashCode() + { + return scriptLanguage.GetHashCode() ^ scriptText.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (obj is Script) + { + Script other = (Script)obj; + if (!scriptLanguage.Equals(other.scriptLanguage)) + { + return false; + } + if (!scriptText.Equals(other.scriptText)) + { + return false; + } + return true; + } + return false; + } + + public override string ToString() + { + // Text can be large, probably should not be included. + return "Script: " + scriptLanguage; + } + } + #endregion + + #region ScriptBuilder + /// + /// Builder for . + /// + public class ScriptBuilder + { + /// + /// Creates a new ScriptBuilder. + /// + public ScriptBuilder() + { + } + + /// + /// Gets/sets the language of the script. + /// + public string ScriptLanguage + { + get; + set; + } + + /// + /// Gets/sets the text of the script. + /// + public string ScriptText + { + get; + set; + } + + /// + /// Creates a Script. + /// + /// + /// Prior to calling this method the language + /// and the text should have been set. + /// + /// a new script; never null. + public Script Build() + { + return new Script(ScriptLanguage, ScriptText); + } + } + #endregion + + #region ScriptContext + /// + /// Encapsulates a script and all of its parameters. + /// + /// + /// + public sealed class ScriptContext + { + private readonly String _scriptLanguage; + private readonly String _scriptText; + private readonly IDictionary _scriptArguments; + + /// + /// Public only for serialization; please use . + /// + /// The script language. Must not be null. + /// The script text. Must not be null. + /// The script arguments. May be null. + public ScriptContext(String scriptLanguage, + String scriptText, + IDictionary scriptArguments) + { + + if (StringUtil.IsBlank(scriptLanguage)) + { + throw new ArgumentException("Argument 'scriptLanguage' must be specified"); + } + if (StringUtil.IsBlank(scriptText)) + { + throw new ArgumentException("Argument 'scriptText' must be specified"); + } + //clone script arguments and options - this serves two purposes + //1)makes sure everthing is serializable + //2)does a deep copy + IDictionary scriptArgumentsClone = (IDictionary)SerializerUtil.CloneObject(scriptArguments); + _scriptLanguage = scriptLanguage; + _scriptText = scriptText; + _scriptArguments = CollectionUtil.NewReadOnlyDictionary(scriptArgumentsClone); + } + + /// + /// Identifies the language in which the script is written + /// (e.g., bash, csh, + /// Perl4 or Python). + /// + /// The script language. + public String ScriptLanguage + { + get + { + return _scriptLanguage; + } + } + + /// + /// Returns the text (i.e., actual characters) of the script. + /// + /// The text of the script. + public String ScriptText + { + get + { + return _scriptText; + } + } + + /// + /// Returns a map of arguments to be passed to the script. + /// + /// + /// Values must be types that the framework can serialize. + /// See for a list of supported types. + /// + /// A map of arguments to be passed to the script. + public IDictionary ScriptArguments + { + get + { + return _scriptArguments; + } + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("ScriptContext: "); + // poor man's to string method. + IDictionary map = new Dictionary(); + map["Language"] = ScriptLanguage; + map["Text"] = ScriptText; + map["Arguments"] = ScriptArguments; + bld.Append(map.ToString()); + return bld.ToString(); + } + + } + #endregion + + #region ScriptContextBuilder + /// + /// Builds an . + /// + public sealed class ScriptContextBuilder + { + private String _scriptLanguage; + private String _scriptText; + private readonly IDictionary _scriptArguments = new + Dictionary(); + + /// + /// Creates an empty builder. + /// + public ScriptContextBuilder() + { + + } + + /// + /// Creates a builder with the required parameters specified. + /// + /// a string that identifies the language + /// in which the script is written + /// (e.g., bash, csh, + /// Perl4 or Python). + /// The text (i.e., actual characters) of the script. + public ScriptContextBuilder(String scriptLanguage, + String scriptText) + { + _scriptLanguage = scriptLanguage; + _scriptText = scriptText; + } + + /// + /// Identifies the language in which the script is written + /// (e.g., bash, csh, + /// Perl4 or Python). + /// + /// The script language. + public String ScriptLanguage + { + get + { + return _scriptLanguage; + } + set + { + _scriptLanguage = value; + } + } + + /// + /// Returns the actual characters of the script. + /// + /// the actual characters of the script. + public String ScriptText + { + get + { + return _scriptText; + } + set + { + _scriptText = value; + } + } + + /// + /// Adds or sets an argument to pass to the script. + /// + /// The name of the argument. Must not be null. + /// The value of the argument. Must be one of + /// type types that the framework can serialize. + /// + public ScriptContextBuilder AddScriptArgument(String name, Object value) + { + if (name == null) + { + throw new ArgumentException("Argument 'name' cannot be null."); + } + //don't validate value here - we do that implicitly when + //we clone in the constructor of ScriptRequest + _scriptArguments[name] = value; + return this; + } + + /// + /// Removes the given script argument. + /// + /// The name of the argument. Must not be null. + public ScriptContextBuilder RemoveScriptArgument(String name) + { + if (name == null) + { + throw new ArgumentException("Argument 'name' cannot be null."); + } + _scriptArguments.Remove(name); + return this; + } + + /// + /// Returns a mutable reference of the script arguments map. + /// + /// A mutable reference of the script arguments map. + public IDictionary ScriptArguments + { + get + { + //might as well be mutable since it's the builder and + //we don't want to deep copy anyway + return _scriptArguments; + } + } + + /// + /// Creates a ScriptContext. + /// + /// + /// The scriptLanguage and scriptText + /// must be set prior to calling this. + /// + /// The ScriptContext. + public ScriptContext Build() + { + return new ScriptContext(_scriptLanguage, + _scriptText, + _scriptArguments); + } + } + #endregion + + #region SearchResult + /// + /// The final result of a query request returned after all connector objects + /// matching the request have been returned. In addition to indicating that no + /// more objects are to be returned by the search, the search result will contain + /// page results state information if result paging has been enabled for the + /// search. + /// + /// Since 1.4 + public sealed class SearchResult + { + + /// + /// An enum of count policy types. + /// + /// + /// + public enum CountPolicy + { + /// + /// There should be no count returned. No overhead should be incurred. + /// + NONE, + + /// + /// Estimated count may be used. If no estimation is available it is up + /// to the implementor whether to return an count or + /// . It should be known to the client which was used as in + /// + /// + ESTIMATE, + + /// + /// Exact count is required. + /// + EXACT + } + + /// + /// The value provided when no count is known or can reasonably be supplied. + /// + public const int NoCount = -1; + + private readonly string _pagedResultsCookie; + private readonly CountPolicy _totalPagedResultsPolicy; + private readonly int _totalPagedResults; + private readonly int _remainingPagedResults; + + /// + /// Creates a new search result with a {@code null} paged results cookie and + /// no estimate of the total number of remaining results. + /// + public SearchResult() + : this(null, CountPolicy.NONE, NoCount, NoCount) + { + } + + /// + /// Creates a new search result with the provided paged results cookie and + /// estimate of the total number of remaining results. + /// + /// + /// The opaque cookie which should be used with the next paged + /// results search request, or {@code null} if paged results were + /// not requested, or if there are not more pages to be returned. + /// + /// An estimate of the total number of remaining results to be + /// returned in subsequent paged results search requests, or + /// {@code -1} if paged results were not requested, or if the + /// total number of remaining results is unknown. + public SearchResult(string pagedResultsCookie, int remainingPagedResults) + : this(pagedResultsCookie, CountPolicy.NONE, NoCount, remainingPagedResults) + { + } + + /// + /// Creates a new query response with the provided paged results cookie and a + /// count of the total number of resources according to + /// . + /// + /// + /// The opaque cookie which should be used with the next paged + /// results query request, or {@code null} if paged results were + /// not requested, or if there are not more pages to be returned. + /// + /// The policy that was used to calculate + /// . If none is specified ({@code null} + /// ), then is assumed. + /// + /// The total number of paged results requested in adherence to + /// the in + /// the request, or if paged results were not + /// requested, the count policy is {@code NONE}, or if the total + /// number of results is unknown. + /// + /// An estimate of the total number of remaining results to be + /// returned in subsequent paged results query requests, or + /// {@code -1} if paged results were not requested, or if the + /// total number of remaining results is unknown. + /// Since 1.5 + public SearchResult(string pagedResultsCookie, CountPolicy totalPagedResultsPolicy, int totalPagedResults, int remainingPagedResults) + { + _pagedResultsCookie = pagedResultsCookie; + _totalPagedResultsPolicy = totalPagedResultsPolicy; + _totalPagedResults = totalPagedResults; + _remainingPagedResults = remainingPagedResults; + } + + /// + /// Returns the opaque cookie which should be used with the next paged + /// results search request. + /// + /// The opaque cookie which should be used with the next paged + /// results search request, or {@code null} if paged results were not + /// requested, or if there are not more pages to be returned. + public string PagedResultsCookie + { + get + { + return _pagedResultsCookie; + } + } + + /// + /// Returns the policy that was used to calculate the + /// {@literal totalPagedResults}. + /// + /// The count policy. + /// Since 1.5 + public CountPolicy TotalPagedResultsPolicy + { + get + { + return _totalPagedResultsPolicy; + } + } + + /// + /// Returns the total number of paged results in adherence with the + /// in the request or + /// if paged results were not requested, the count policy + /// is {@code NONE}, or the total number of paged results is unknown. + /// + /// A count of the total number of paged results to be returned in + /// subsequent paged results query requests, or if + /// paged results were not requested, or if the total number of paged + /// results is unknown. + /// Since 1.5 + public int TotalPagedResults + { + get + { + return _totalPagedResults; + } + } + + /// + /// Returns an estimate of the total number of remaining results to be + /// returned in subsequent paged results search requests. + /// + /// An estimate of the total number of remaining results to be + /// returned in subsequent paged results search requests, or + /// {@code -1} if paged results were not requested, or if the total + /// number of remaining results is unknown. + public int RemainingPagedResults + { + get + { + return _remainingPagedResults; + } + } + + } + #endregion + + #region SortKey + /// + /// A sort key which can be used to specify the order in which connector objects + /// should be included in the results of a search request. + /// + /// + /// Since 1.4 + public sealed class SortKey + { + + private readonly string _field; + private readonly bool _isAscendingOrder; + + public SortKey(string field, bool isAscendingOrder) + { + this._field = Assertions.BlankChecked(field, "field"); + this._isAscendingOrder = isAscendingOrder; + } + + /// + /// Returns the sort key field. + /// + /// The sort key field. + public string Field + { + get + { + return _field; + } + } + + /// + /// Returns {@code true} if this sort key is in ascending order, or + /// {@code false} if it is in descending order. + /// + /// + /// {@code true} if this sort key is in ascending order, or + /// {@code false} if it is in descending ord)er. + /// + public bool IsAscendingOrder() + { + return _isAscendingOrder; + } + + /// + /// Creates a new ascending-order sort key for the provided field. + /// + /// + /// The sort key field. + /// + /// A new ascending-order sort key. + /// + /// If {@code field} is not a valid attribute name. + /// + public static SortKey AscendingOrder(string field) + { + return new SortKey(field, true); + } + + /// + /// Creates a new descending-order sort key for the provided field. + /// + /// + /// The sort key field. + /// + /// A new descending-order sort key. + /// + /// If {@code field} is not a valid attribute name. + /// + public static SortKey DescendingOrder(string field) + { + return new SortKey(field, false); + } + + /// + /// Creates a new sort key having the same field as the provided key, but in + /// reverse sort order. + /// + /// + /// The sort key to be reversed. + /// + /// The reversed sort key. + public static SortKey ReverseOrder(SortKey key) + { + return new SortKey(key._field, !key._isAscendingOrder); + } + } + #endregion + + #region ISubscription + /// + /// A SubscriptionHandler represents a subscription to an asynchronous event + /// channel. + /// + /// since 1.5 + public interface ISubscription : IDisposable + { + /// + /// Indicates whether this Subscription is currently unsubscribed. + /// + /// true if this Subscription is currently + /// unsubscribed, false otherwise + bool Unsubscribed { get; } + } + + public class CancellationSubscription : ISubscription + { + private readonly CancellationTokenSource _cts; + + /// + /// Gets the used by this CancellationSubscription. + /// + /// + public CancellationToken Token + { + get + { + return _cts.Token; + } + } + + /// + /// Gets a value that indicates whether the object is disposed. + /// + /// + public bool IsDisposed + { + get + { + return _cts.IsCancellationRequested; + } + } + + /// + /// Initializes a new instance of the + /// class that uses an existing . + /// + /// + /// used for cancellation. + /// is null. + public CancellationSubscription(CancellationTokenSource cts) + { + if (cts == null) + throw new ArgumentNullException("cts"); + _cts = cts; + } + + /// + /// Initializes a new instance of the class that uses a + /// new . + /// + /// + public CancellationSubscription() + : this(new CancellationTokenSource()) + { + } + + /// + /// Cancels the underlying . + /// + /// + public void Dispose() + { + _cts.Cancel(); + } + + /// + /// Indicates whether this Subscription is currently unsubscribed. + /// + /// true if this Subscription is currently + /// unsubscribed, false otherwise + public bool Unsubscribed + { + get { return IsDisposed; } + } + } + + #endregion + + #region SyncDelta + /// + /// Represents a change to an object in a resource. + /// + /// + /// + public sealed class SyncDelta + { + private readonly SyncToken _token; + private readonly SyncDeltaType _deltaType; + private readonly Uid _previousUid; + private readonly ObjectClass _objectClass; + private readonly Uid _uid; + private readonly ConnectorObject _object; + + /// + /// Creates a SyncDelata + /// + /// The token. Must not be null. + /// The delta. Must not be null. + /// The previousUid. Can be null. + /// The objectClass. Can be null. + /// The uid. Must not be null. + /// The object that has changed. May be null for delete. + internal SyncDelta(SyncToken token, SyncDeltaType deltaType, + Uid previousUid, ObjectClass objectClass, Uid uid, + ConnectorObject obj) + { + Assertions.NullCheck(token, "token"); + Assertions.NullCheck(deltaType, "deltaType"); + Assertions.NullCheck(uid, "uid"); + + //do not allow previous Uid for anything else than create or update + if (previousUid != null && (deltaType == SyncDeltaType.DELETE || deltaType == SyncDeltaType.CREATE)) + { + throw new ArgumentException("The previous Uid can only be specified for create_or_update or udate."); + } + + //only allow null object for delete + if (obj == null && + deltaType != SyncDeltaType.DELETE) + { + throw new ArgumentException("ConnectorObject must be specified for anything other than delete."); + } + + //if object not null, make sure its Uid + //matches + if (obj != null) + { + if (!uid.Equals(obj.Uid)) + { + throw new ArgumentException("Uid does not match that of the object."); + } + if (!objectClass.Equals(obj.ObjectClass)) + { + throw new ArgumentException("ObjectClass does not match that of the object."); + } + } + + _token = token; + _deltaType = deltaType; + _previousUid = previousUid; + _objectClass = objectClass; + _uid = uid; + _object = obj; + + } + + /// + /// If the change described by this SyncDelta modified the + /// object's Uid, this method returns the Uid before the change. + /// + /// + /// Not + /// all resources can determine the previous Uid, so this method can + /// return null. + /// + /// the previous Uid or null if it could not be determined + /// or the change did not modify the Uid. + public Uid PreviousUid + { + get + { + return _previousUid; + } + } + + /// + /// If the change described by this SyncDelta.DELETE and the + /// deleted object value is null, this method returns the + /// ObjectClass of the deleted object. If operation syncs + /// + /// this must be set, otherwise this method can return null. + /// + /// the ObjectClass of the deleted object. + public ObjectClass ObjectClass + { + get + { + return _objectClass; + } + } + + /// + /// Returns the Uid of the object that changed. + /// + /// the Uid of the object that changed. + public Uid Uid + { + get + { + return _uid; + } + } + + /// + /// Returns the connector object that changed. + /// + /// + /// This + /// may be null in the case of delete. + /// + /// The object or possibly null if this + /// represents a delete. + public ConnectorObject Object + { + get + { + return _object; + } + } + + /// + /// Returns the SyncToken of the object that changed. + /// + /// the SyncToken of the object that changed. + public SyncToken Token + { + get + { + return _token; + } + } + + /// + /// Returns the type of the change the occured. + /// + /// The type of change that occured. + public SyncDeltaType DeltaType + { + get + { + return _deltaType; + } + } + + + public override String ToString() + { + IDictionary values = new Dictionary(); + values["Token"] = _token; + values["DeltaType"] = _deltaType; + values["PreviousUid"] = _previousUid; + values["ObjectClass"] = _objectClass; + values["Uid"] = _uid; + values["Object"] = _object; + return values.ToString(); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = (_token != null ? _token.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (int)_deltaType; + hashCode = (hashCode * 397) ^ (_previousUid != null ? _previousUid.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (_objectClass != null ? _objectClass.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (_uid != null ? _uid.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (_object != null ? _object.GetHashCode() : 0); + return hashCode; + } + } + + private bool Equals(SyncDelta other) + { + return Equals(_token, other._token) && _deltaType == other._deltaType && Equals(_previousUid, other._previousUid) && Equals(_objectClass, other._objectClass) && Equals(_uid, other._uid) && Equals(_object, other._object); + } + + public override bool Equals(Object o) + { + if (o is SyncDelta) + { + SyncDelta other = (SyncDelta)o; + if (!_token.Equals(other._token)) + { + return false; + } + if (!_deltaType.Equals(other._deltaType)) + { + return false; + } + if (_previousUid == null) + { + if (other._previousUid != null) + { + return false; + } + } + else if (!_previousUid.Equals(other._previousUid)) + { + return false; + } + if (_objectClass == null) + { + if (other._objectClass != null) + { + return false; + } + } + else if (!_objectClass.Equals(other._objectClass)) + { + return false; + } + if (!_uid.Equals(other._uid)) + { + return false; + } + if (_object == null) + { + if (other._object != null) + { + return false; + } + } + else if (!_object.Equals(other._object)) + { + return false; + } + return true; + } + return false; + } + } + #endregion + + #region SyncDeltaBuilder + /// + /// Builder for . + /// + public sealed class SyncDeltaBuilder + { + private SyncToken _token; + private SyncDeltaType _deltaType; + private Uid _previousUid; + private ObjectClass _objectClass; + private Uid _uid; + private ConnectorObject _object; + + /// + /// Create a new SyncDeltaBuilder + /// + public SyncDeltaBuilder() + { + + } + + /// + /// Creates a new SyncDeltaBuilder whose + /// values are initialized to those of the delta. + /// + /// The original delta. + public SyncDeltaBuilder(SyncDelta delta) + { + _token = delta.Token; + _deltaType = delta.DeltaType; + _previousUid = delta.PreviousUid; + _objectClass = delta.ObjectClass; + _uid = delta.Uid; + _object = delta.Object; + } + + /// + /// Returns the SyncToken of the object that changed. + /// + /// the SyncToken of the object that changed. + public SyncToken Token + { + get + { + return _token; + } + set + { + _token = value; + } + } + + /// + /// Returns the type of the change that occurred. + /// + /// The type of change that occurred. + public SyncDeltaType DeltaType + { + get + { + return _deltaType; + } + set + { + _deltaType = value; + } + } + + /// + /// Returns the Uid before the change. + /// + /// the Uid before the change. + public Uid PreviousUid + { + get + { + return _previousUid; + } + set + { + _previousUid = value; + } + } + + /// + /// Returns the ObjectClass of Deleted object. + /// + /// the ObjectClass of Deleted object. + public ObjectClass ObjectClass + { + get + { + return _objectClass; + } + set + { + _objectClass = value; + } + } + + + /// + /// Returns the Uid of the object that changed. + /// + /// + /// Note that this is implicitly set when you call + /// . + /// + /// the Uid of the object that changed. + public Uid Uid + { + get + { + return _uid; + } + set + { + _uid = value; + } + } + + /// + /// Returns the object that changed. + /// + /// + /// Sets the object that changed and implicitly + /// sets Uid if object is not null. + /// + /// The object that changed. May be null for + /// deletes. + public ConnectorObject Object + { + get + { + return _object; + } + set + { + _object = value; + if (value != null) + { + _uid = value.Uid; + _objectClass = value.ObjectClass; + } + } + } + + /// + /// Creates a SyncDelta. + /// + /// + /// Prior to calling the following must be specified: + /// + /// + /// (for anything other than delete) + /// + /// + /// + /// (this is implictly set when calling ) + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public SyncDelta Build() + { + return new SyncDelta(_token, _deltaType, _previousUid, _objectClass, _uid, _object); + } + } + #endregion + + #region SyncDeltaType + /// + /// The type of change. + /// + public enum SyncDeltaType + { + /// + /// The change represents either a create or an update in + /// the resource. + /// + /// + /// These are combined into a single value because: + /// + /// + /// Many resources will not be able to distinguish a create from an update. + /// Those that have an audit log will be able to. However, many implementations + /// will only have the current record and a modification timestamp. + /// + /// + /// + /// Regardless of whether or not the resource can distinguish the two cases, + /// the application needs to distinguish. + /// + /// + /// + /// + CREATE_OR_UPDATE, + + /// + /// The change represents a DELETE in the resource + /// + DELETE, + + /// + /// The change represents a CREATE in the resource + /// + /// + /// Experimental type to support better event mechanism where it's possible. + /// @see #CREATE_OR_UPDATE + /// + CREATE, + + /// + /// The change represents a UPDATE in the resource + /// + /// + /// Experimental type to support better event mechanism where it's possible. + /// @see #CREATE_OR_UPDATE + /// + UPDATE + } + #endregion + + #region SyncResultsHandler + public class SyncResultsHandler + { + /// + /// Called to handle a delta in the stream. + /// + /// + /// Will be called multiple times, + /// once for each result. Although a callback, this is still invoked + /// synchronously. That is, it is guaranteed that following a call to + /// no + /// more invocations to will be performed. + /// + /// The change + /// True if the application wants to continue processing more + /// results. + /// If the application encounters an exception. This will stop + /// the interation and the exception will be propogated back to + /// the application. + public Func Handle; + } + #endregion + + #region SyncToken + /// + /// Abstract "place-holder" for synchronization. + /// + /// + /// The application must not make + /// any attempt to interpret the value of the token. From the standpoint of the + /// application the token is merely a black-box. The application may only persist + /// the value of the token for use on subsequent synchronization requests. + /// + /// What this token represents is entirely connector-specific. On some connectors + /// this might be a last-modified value. On others, it might be a unique ID of a + /// log table entry. On others such as JMS, this might be a dummy value since + /// JMS itself keeps track of the state of the sync. + /// + /// + public sealed class SyncToken + { + + private Object _value; + + /// + /// Creates a new + /// + /// May not be null. TODO: define set of allowed value types + /// (currently same as set of allowed attribute values). + public SyncToken(Object value) + { + Assertions.NullCheck(value, "value"); + FrameworkUtil.CheckAttributeValue(value); + _value = value; + } + + /// + /// Returns the value for the token. + /// + /// The value for the token. + public Object Value + { + get + { + return _value; + } + } + + public override String ToString() + { + return "SyncToken: " + _value.ToString(); + } + + public override int GetHashCode() + { + return CollectionUtil.GetHashCode(_value); + } + + public override bool Equals(Object o) + { + if (o is SyncToken) + { + SyncToken other = (SyncToken)o; + return CollectionUtil.Equals(_value, other._value); + } + return false; + } + + + } + #endregion + + #region Uid + public sealed class Uid : ConnectorAttribute + { + + public static readonly string NAME = ConnectorAttributeUtil.CreateSpecialName("UID"); + + private readonly String _revision; + + public Uid(String value) + : base(NAME, CollectionUtil.NewReadOnlyList(Check(value))) + { + _revision = null; + } + + public Uid(String value, string revision) + : base(NAME, CollectionUtil.NewReadOnlyList(Check(value))) + { + if (StringUtil.IsBlank(revision)) + { + throw new System.ArgumentException("Revision value must not be blank!"); + } + this._revision = revision; + } + + /// + /// Throws an if the value passed in blank. + /// + private static String Check(String value) + { + if (StringUtil.IsBlank(value)) + { + String ERR = "Uid value must not be blank!"; + throw new ArgumentException(ERR); + } + return value; + } + + /// + /// Obtain a string representation of the value of this attribute, which + /// value uniquely identifies a on the target + /// resource. + /// + /// value that uniquely identifies an object. + public String GetUidValue() + { + return ConnectorAttributeUtil.GetStringValue(this); + } + + /// + /// Return the string representation of the revision value of the + ///

+ /// The revision number specifies a given version ot the + /// identified by the + /// + ///

+ /// null if the connector does not support the MVCC and does not set + /// this value otherwise return the revision number of the object. + public string Revision + { + get + { + return _revision; + } + } + } + #endregion + + #region ConnectorAttributesAccessor + /// + /// Attributes Accessor convenience methods for accessing attributes. + /// + /// + /// This class wraps a set of attributes to make lookup faster than the + /// method, since that method must + /// re-create the map each time. + /// + /// Warren Strange + public class ConnectorAttributesAccessor + { + + IDictionary _attrMap; + + public ConnectorAttributesAccessor(ICollection attrs) + { + _attrMap = ConnectorAttributeUtil.ToMap(attrs); + } + + /// + /// Find the named attribute + /// + /// - + /// the attribute name to search for + /// the Attribute, or null if not found. + public ConnectorAttribute Find(String name) + { + return CollectionUtil.GetValue(_attrMap, name, null); + } + + /// + /// Get the attribute from the set of attributes. + /// + /// the attribute in the set. + public Name GetName() + { + return (Name)Find(Name.NAME); + } + + /// + /// Get the attribute from the set of attributes. + /// + /// the attribute in the set. + public Uid GetUid() + { + return (Uid)Find(Uid.NAME); + } + + /// + /// Return the enabled status of the account. + /// + /// + /// If the ENABLE operational attribute is present, it's value takes + /// precedence over the current value. If it is missing, the currentlyEnabled + /// status is returned instead. + /// + /// the default state if enable is not found. + /// true if the account is enabled, false otherwise + public bool GetEnabled(bool dflt) + { + bool e = dflt; + ConnectorAttribute enable = Find(OperationalAttributes.ENABLE_NAME); + if (enable != null) + { + e = ConnectorAttributeUtil.GetBooleanValue(enable).Value; + } + return e; + } + + /// + /// Get the password as a GuardeString + /// + /// the password as a guarded String + public GuardedString GetPassword() + { + ConnectorAttribute a = Find(OperationalAttributes.PASSWORD_NAME); + return a == null ? null : ConnectorAttributeUtil.GetGuardedStringValue(a); + } + + /// + /// Return a list of attributes + /// + /// - + /// name of attribute to search for. + /// The List (generic object) if it exists otherwise null. + public IList FindList(String name) + { + ConnectorAttribute a = Find(name); + return (a == null) ? null : a.Value; + } + + /// + /// Return the multivalued attribute as a list of strings. + /// + /// + /// This will throw a + /// ClassCastException if the underlying attribute list is not of type + /// String. + /// + /// the name of the attribute to search for + /// a List of String values for the attribute + public IList FindStringList(String name) + { + IList l = FindList(name); + if (l != null) + { + IList ret = new List(l.Count); + foreach (object o in l) + { + ret.Add((String)o); + } + return ret; + } + return null; + } + + /// + /// Get the name of attributes this Accessor was created with. + /// + /// new Case Insensitive ReadOnly Set of attribute name the access + /// has access to. + /// + /// Since 1.4 + public ICollection ListAttributeNames() + { + //ICollection names = CollectionUtil.NewCaseInsensitiveSet(); + //CollectionUtil.AddAll(names, _attrMap.Keys); + return CollectionUtil.AsReadOnlySet(_attrMap.Keys); + } + + /// + /// Determines if the set as the attribute specified. + /// + /// attribute name + /// true if the named attribute exists, false otherwise + public bool HasAttribute(String name) + { + return Find(name) != null; + } + + /// + /// Get the string value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the string value. + /// null if the value is null otherwise the string value for the + /// attribute. + /// if the object in the attribute is not a string. + /// if the attribute is a multi-valued (rather than + /// single-valued). + public String FindString(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetStringValue(a); + } + + /// + /// Get the char value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the char value. + /// null if the value is null otherwise the char value for the + /// attribute. + /// if the object in the attribute is not a char. + /// if the attribute is a multi-valued (rather than + /// single-valued). + /// Since 1.4 + public char? FindCharacter(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetCharacterValue(a); + } + + /// + /// Get the integer value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the long value. + /// null if the value is null otherwise the long value for the + /// attribute. + /// if the object in the attribute is not an long. + /// if the attribute is a multi-valued (rather than + /// single-valued). + public int? FindInteger(String name) + { + ConnectorAttribute a = Find(name); + return (a == null) ? null : ConnectorAttributeUtil.GetIntegerValue(a); + } + + /// + /// Get the long value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the long value. + /// null if the value is null otherwise the long value for the + /// attribute. + /// if the object in the attribute is not an long. + /// if the attribute is a multi-valued (rather than + /// single-valued). + public long? FindLong(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetLongValue(a); + } + + /// + /// Get the date value from the specified (single-valued) attribute that + /// contains a long. + /// + /// Attribute from which to retrieve the date value. + /// null if the value is null otherwise the date value for the + /// attribute. + /// if the object in the attribute is not an long. + /// if the attribute is a multi-valued (rather than + /// single-valued). + public DateTime? FindDateTime(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetDateTimeValue(a); + } + + /// + /// Get the integer value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the integer value. + /// null if the value is null otherwise the integer value for the + /// attribute. + /// if the object in the attribute is not an integer. + /// if the attribute is a multi-valued (rather than + /// single-valued).. + public double? FindDouble(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetDoubleValue(a); + } + + /// + /// Get the float value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the float value. + /// null if the value is null otherwise the float value for the + /// attribute. + /// if the object in the attribute is not a float. + /// if the attribute is a multi-valued (rather than + /// single-valued).. + /// Since 1.4 + public float? FindFloat(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetFloatValue(a); + } + + /// + /// Get the big decimal value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the big decimal value. + /// null if the value is null otherwise the big decimal value for the + /// attribute. + /// if the object in the attribute is not a big decimal. + /// if the attribute is a multi-valued (rather than + /// single-valued).. + /// Since 1.4 + public BigDecimal FindBigDecimal(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetBigDecimalValue(a); + } + + /// + /// Get the boolean value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the boolean value. + /// null if the value is null otherwise the boolean value for the + /// attribute. + /// if the object in the attribute is not an . + /// if the attribute is a multi-valued (rather than + /// single-valued). + public bool? FindBoolean(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetBooleanValue(a); + } + + /// + /// Get the byte value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the byte value. + /// null if the value is null otherwise the byte value for the + /// attribute. + /// if the object in the attribute is not a byte. + /// if the attribute is a multi-valued (rather than + /// single-valued).. + /// Since 1.4 + public byte? FindByte(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetByteValue(a); + } + + /// + /// Get the byte array value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the byte array value. + /// null if the value is null otherwise the byte array value for the + /// attribute. + /// if the object in the attribute is not a byte array. + /// if the attribute is a multi-valued (rather than + /// single-valued).. + /// Since 1.4 + public byte[] FindByteArray(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetByteArrayValue(a); + } + + /// + /// Get the big integer value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the big integer value. + /// null if the value is null otherwise the big integer value for the + /// attribute. + /// if the object in the attribute is not a big integer. + /// if the attribute is a multi-valued (rather than + /// single-valued).. + /// Since 1.4 + public BigInteger FindBigInteger(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetBigIntegerValue(a); + } + + /// + /// Get the guarded byte array value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the guarded byte array value. + /// null if the value is null otherwise the guarded byte array value for the + /// attribute. + /// if the object in the attribute is not a guarded byte array. + /// if the attribute is a multi-valued (rather than + /// single-valued).. + /// Since 1.4 + public GuardedByteArray FindGuardedByteArray(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetGuardedByteArrayValue(a); + } + + /// + /// Get the guarded string value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the guarded string value. + /// null if the value is null otherwise the guarded string value for the + /// attribute. + /// if the object in the attribute is not a guarded string. + /// if the attribute is a multi-valued (rather than + /// single-valued).. + /// Since 1.4 + public GuardedString FindGuardedString(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetGuardedStringValue(a); + } + + /// + /// Get the dictionary value from the specified (single-valued) attribute. + /// + /// Attribute from which to retrieve the dictionary value. + /// null if the value is null otherwise the byte dictionary for the + /// attribute. + /// if the object in the attribute is not a dictionary. + /// if the attribute is a multi-valued (rather than + /// single-valued).. + /// Since 1.4 + public IDictionary FindDictionary(String name) + { + ConnectorAttribute a = Find(name); + return a == null ? null : ConnectorAttributeUtil.GetDictionaryValue(a); + } + } + #endregion + + #region CultureInfoCache + internal static class CultureInfoCache + { + private static readonly object LOCK = new Object(); + private static CultureInfo _instance; + + public static CultureInfo Instance + { + get + { + lock (LOCK) + { + if (_instance == null) + { + _instance = CultureInfo.CurrentCulture; + if (_instance == null) + { + _instance = CultureInfo.InstalledUICulture; + } + } + return _instance; + } + } + } + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/Framework/CommonObjectsFilter.cs b/dotnet/framework/Framework/CommonObjectsFilter.cs new file mode 100755 index 00000000..d5c4ae3c --- /dev/null +++ b/dotnet/framework/Framework/CommonObjectsFilter.cs @@ -0,0 +1,2535 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Linq; +using System.Text; +using System.Collections.Generic; +using System.Threading; +using Org.IdentityConnectors.Common; + +namespace Org.IdentityConnectors.Framework.Common.Objects.Filters +{ + #region AbstractFilterTranslator + /// + /// Base class to make it easier to implement Search. + /// + /// + /// A search filter may contain operators (such as 'contains' or 'in') + /// or may contain logical operators (such as 'AND', 'OR' or 'NOT') + /// that a connector cannot implement using the native API + /// of the target system or application. + /// A connector developer should subclass AbstractFilterTranslator + /// in order to declare which filter operations the connector does support. + /// This allows the FilterTranslator instance to analyze + /// a specified search filter and reduce the filter to its most efficient form. + /// The default (and worst-case) behavior is to return a null expression, + /// which means that the connector should return "everything" + /// (that is, should return all values for every requested attribute) + /// and rely on the common code in the framework to perform filtering. + /// This "fallback" behavior is good (in that it ensures consistency + /// of search behavior across connector implementations) but it is + /// obviously better for performance and scalability if each connector + /// performs as much filtering as the native API of the target can support. + /// + /// A subclass should override each of the following methods where possible: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Translation can then be performed using . + /// + /// + /// + /// + /// The result type of the translator. Commonly this will + /// be a string, but there are cases where you might need to return + /// a more complex data structure. For example if you are building a SQL + /// query, you will need not *just* the base WHERE clause but a list + /// of tables that need to be joined together. + abstract public class AbstractFilterTranslator : FilterTranslator + where T : class + { + /// + /// Main method to be called to translate a filter + /// + /// The filter to translate. + /// The list of queries to be performed. The list + /// size() may be one of the following: + /// + /// + /// 0 - This + /// signifies fetch everything. This may occur if your filter + /// was null or one of your create* methods returned null. + /// + /// + /// + /// 1 - List contains a single query that will return the results from the filter. + /// Note that the results may be a superset of those specified by + /// the filter in the case that one of your create* methods returned null. + /// That is OK from a behavior standpoint since ConnectorFacade performs + /// a second level of filtering. However it is undesirable from a performance standpoint. + /// + /// + /// + /// >1 - List contains multiple queries that must be performed in order to + /// meet the filter that was passed in. Note that this only occurs if your + /// method can return null. If this happens, it + /// is the responsibility of the connector implementor to perform each query + /// and combine the results. In order to eliminate duplicates, the connector + /// implementation must keep an in-memory HashSet of those UID + /// that have been visited thus far. This will not scale well if your + /// result sets are large. Therefore it is recommended that if + /// at all possible you implement + /// + /// + /// + public IList Translate(Filter filter) + { + if (filter == null) + { + return new List(); + } + //this must come first + filter = EliminateExternallyChainedFilters(filter); + filter = NormalizeNot(filter); + filter = SimplifyAndDistribute(filter); + //might have simplified it to the everything filter + if (filter == null) + { + return new List(); + } + IList result = TranslateInternal(filter); + //now "optimize" - we can eliminate exact matches at least + HashSet set = new HashSet(); + IList optimized = new List(result.Count); + foreach (T obj in result) + { + if (set.Add(obj)) + { + optimized.Add(obj); + } + } + return optimized; + } + + private Filter EliminateExternallyChainedFilters(Filter filter) + { + while (filter is ExternallyChainedFilter) + { + filter = ((ExternallyChainedFilter)filter).Filter; + } + return filter; + } + + /// + /// Pushes Not's so that they are just before the leaves of the tree + /// + private Filter NormalizeNot(Filter filter) + { + if (filter is AndFilter) + { + AndFilter af = (AndFilter)filter; + return new AndFilter(NormalizeNot(af.Left), + NormalizeNot(af.Right)); + } + else if (filter is OrFilter) + { + OrFilter of = (OrFilter)filter; + return new OrFilter(NormalizeNot(of.Left), + NormalizeNot(of.Right)); + } + else if (filter is NotFilter) + { + NotFilter nf = (NotFilter)filter; + return Negate(NormalizeNot(nf.Filter)); + } + else + { + return filter; + } + } + + /// + /// Given a filter, create a filter representing its negative. + /// + /// + /// This is used by normalizeNot. + /// + private Filter Negate(Filter filter) + { + if (filter is AndFilter) + { + AndFilter af = (AndFilter)filter; + return new OrFilter(Negate(af.Left), + Negate(af.Right)); + } + else if (filter is OrFilter) + { + OrFilter of = (OrFilter)filter; + return new AndFilter(Negate(of.Left), + Negate(of.Right)); + } + else if (filter is NotFilter) + { + NotFilter nf = (NotFilter)filter; + return nf.Filter; + } + else + { + return new NotFilter(filter); + } + } + + /// + /// Simultaneously prunes those portions of the + /// filter than cannot be implemented and distributes + /// Ands over Ors where needed if the resource does not + /// implement Or. + /// + /// Nots must already be normalized + /// a simplified filter or null to represent the + /// "everything" filter. + private Filter SimplifyAndDistribute(Filter filter) + { + if (filter is AndFilter) + { + AndFilter af = (AndFilter)filter; + Filter simplifiedLeft = + SimplifyAndDistribute(af.Left); + Filter simplifiedRight = + SimplifyAndDistribute(af.Right); + if (simplifiedLeft == null) + { + //left is "everything" - just return the right + return simplifiedRight; + } + else if (simplifiedRight == null) + { + //right is "everything" - just return the left + return simplifiedLeft; + } + else + { + //simulate translation of the left and right + //to see where we end up + IList leftExprs = + TranslateInternal(simplifiedLeft); + IList rightExprs = + TranslateInternal(simplifiedRight); + if (leftExprs.Count == 0) + { + //This can happen only when one of the create* methods + //is inconsistent from one invocation to the next + //(simplifiedLeft should have been null + //in the previous 'if' above). + throw new InvalidOperationException("Translation method is inconsistent: " + leftExprs); + } + if (rightExprs.Count == 0) + { + //This can happen only when one of the create* methods + //is inconsistent from one invocation to the next + //(simplifiedRight should have been null + //in the previous 'if' above). + throw new InvalidOperationException("Translation method is inconsistent: " + rightExprs); + } + + //Simulate ANDing each pair(left,right). + //If all of them return null (i.e., "everything"), + //then the request cannot be filtered. + bool anyAndsPossible = false; + foreach (T leftExpr in leftExprs) + { + foreach (T rightExpr in rightExprs) + { + T test = CreateAndExpression( + leftExpr, + rightExpr); + if (test != null) + { + anyAndsPossible = true; + break; + } + } + if (anyAndsPossible) + { + break; + } + } + + //If no AND filtering is possible, + //return whichever of left or right + //contains the fewest expressions. + if (!anyAndsPossible) + { + if (leftExprs.Count <= rightExprs.Count) + { + return simplifiedLeft; + } + else + { + return simplifiedRight; + } + } + + //Since AND filtering is possible for at least + //one expression, let's distribute. + if (leftExprs.Count > 1) + { + //The left can contain more than one expression + //only if the left-hand side is an unimplemented OR. + //Distribute our AND to the left. + OrFilter left = (OrFilter)simplifiedLeft; + OrFilter newFilter = + new OrFilter(new AndFilter(left.Left, + simplifiedRight), + new AndFilter(left.Right, + simplifiedRight)); + return SimplifyAndDistribute(newFilter); + } + else if (rightExprs.Count > 1) + { + //The right can contain more than one expression + //only if the right-hand side is an unimplemented OR. + //Distribute our AND to the right. + OrFilter right = (OrFilter)simplifiedRight; + OrFilter newFilter = + new OrFilter(new AndFilter(simplifiedLeft, + right.Left), + new AndFilter(simplifiedLeft, + right.Right)); + return SimplifyAndDistribute(newFilter); + } + else + { + //Each side contains exactly one expression + //and the translator does implement AND + //(anyAndsPossible must be true + //for them to have hit this branch). + if (!anyAndsPossible) + { + throw new Exception("expected anyAndsPossible"); + } + return new AndFilter(simplifiedLeft, simplifiedRight); + } + } + } + else if (filter is OrFilter) + { + OrFilter of = (OrFilter)filter; + Filter simplifiedLeft = + SimplifyAndDistribute(of.Left); + Filter simplifiedRight = + SimplifyAndDistribute(of.Right); + //If either left or right reduces to "everything", + //then simplify the OR to "everything". + if (simplifiedLeft == null || simplifiedRight == null) + { + return null; + } + //otherwise + return new OrFilter(simplifiedLeft, + simplifiedRight); + } + else + { + //Otherwise, it's a NOT(LEAF) or a LEAF. + //Simulate creating it. + T expr = CreateLeafExpression(filter); + if (expr == null) + { + //If the expression cannot be implemented, + //return the "everything" filter. + return null; + } + else + { + //Otherwise, return the filter. + return filter; + } + } + } + + /// + /// Translates the filter into a list of expressions. + /// + /// + /// The filter must have already been transformed + /// using normalizeNot followed by a simplifyAndDistribute. + /// + /// A filter (normalized, simplified, and distibuted) + /// A list of expressions or empty list for everything. + private IList TranslateInternal(Filter filter) + { + if (filter is AndFilter) + { + T result = TranslateAnd((AndFilter)filter); + IList rv = new List(); + if (result != null) + { + rv.Add(result); + } + return rv; + } + else if (filter is OrFilter) + { + return TranslateOr((OrFilter)filter); + } + else + { + //otherwise it's either a leaf or a NOT (leaf) + T expr = CreateLeafExpression(filter); + IList exprs = new List(); + if (expr != null) + { + exprs.Add(expr); + } + return exprs; + } + } + + private T TranslateAnd(AndFilter filter) + { + IList leftExprs = TranslateInternal(filter.Left); + IList rightExprs = TranslateInternal(filter.Right); + if (leftExprs.Count != 1) + { + //this can happen only if one of the create* methods + //is inconsistent from one invocation to the next + //(at this point we've already been simplified and + //distributed). + throw new InvalidOperationException("Translation method is inconsistent: " + leftExprs); + } + if (rightExprs.Count != 1) + { + //this can happen only if one of the create* methods + //is inconsistent from one invocation to the next + //(at this point we've already been simplified and + //distributed). + throw new InvalidOperationException("Translation method is inconsistent: " + rightExprs); + } + T rv = CreateAndExpression(leftExprs[0], rightExprs[0]); + if (rv == null) + { + //This could happen only if we're inconsistent + //(since the simplify logic already should have removed + //any expression that cannot be filtered). + throw new InvalidOperationException("createAndExpression is inconsistent"); + } + return rv; + } + + private IList TranslateOr(OrFilter filter) + { + IList leftExprs = TranslateInternal(filter.Left); + IList rightExprs = TranslateInternal(filter.Right); + if (leftExprs.Count == 0) + { + //This can happen only if one of the create* methods + //is inconsistent from one invocation to the next. + throw new InvalidOperationException("Translation method is inconsistent"); + } + if (rightExprs.Count == 0) + { + //This can happen only if one of the create* methods + //methods is inconsistent from on invocation to the next. + throw new InvalidOperationException("Translation method is inconsistent"); + } + if (leftExprs.Count == 1 && rightExprs.Count == 1) + { + //If each side contains exactly one expression, + //try to create a combined expression. + T val = CreateOrExpression(leftExprs[0], rightExprs[0]); + if (val != null) + { + IList rv = new List(); + rv.Add(val); + return rv; + } + //Otherwise, fall through + } + + //Return a list of queries from the left and from the right + IList rv2 = new List(leftExprs.Count + rightExprs.Count); + CollectionUtil.AddAll(rv2, leftExprs); + CollectionUtil.AddAll(rv2, rightExprs); + return rv2; + } + + /// + /// Creates an expression for a LEAF or a NOT(leaf) + /// + /// Must be either a leaf or a NOT(leaf) + /// The expression + private T CreateLeafExpression(Filter filter) + { + Filter leafFilter; + bool not; + if (filter is NotFilter) + { + NotFilter nf = (NotFilter)filter; + leafFilter = nf.Filter; + not = true; + } + else + { + leafFilter = filter; + not = false; + } + T expr = CreateLeafExpression(leafFilter, not); + return expr; + } + + /// + /// Creates a Leaf expression + /// + /// Must be a leaf expression + /// Is ! to be applied to the leaf expression + /// The expression or null (for everything) + private T CreateLeafExpression(Filter filter, bool not) + { + if (filter is ContainsFilter) + { + return CreateContainsExpression((ContainsFilter)filter, not); + } + else if (filter is EndsWithFilter) + { + return CreateEndsWithExpression((EndsWithFilter)filter, not); + } + else if (filter is EqualsFilter) + { + return CreateEqualsExpression((EqualsFilter)filter, not); + } + else if (filter is GreaterThanFilter) + { + return CreateGreaterThanExpression((GreaterThanFilter)filter, not); + } + else if (filter is GreaterThanOrEqualFilter) + { + return CreateGreaterThanOrEqualExpression((GreaterThanOrEqualFilter)filter, not); + } + else if (filter is LessThanFilter) + { + return CreateLessThanExpression((LessThanFilter)filter, not); + } + else if (filter is LessThanOrEqualFilter) + { + return CreateLessThanOrEqualExpression((LessThanOrEqualFilter)filter, not); + } + else if (filter is StartsWithFilter) + { + return CreateStartsWithExpression((StartsWithFilter)filter, not); + } + else if (filter is ContainsAllValuesFilter) + { + return CreateContainsAllValuesExpression((ContainsAllValuesFilter)filter, not); + } + else + { + //unrecognized expression - nothing we can do + return null; + } + } + + /// + /// Should be overridden by subclasses to create an AND expression + /// if the native resource supports AND. + /// + /// The left expression. Will never be null. + /// The right expression. Will never be null. + /// The AND expression. A return value of null means + /// a native AND query cannot be created for the given expressions. + /// In this case, the resulting query will consist of the + /// leftExpression only. + protected virtual T CreateAndExpression(T leftExpression, T rightExpression) + { + return null; + } + + /// + /// Should be overridden by subclasses to create an OR expression + /// if the native resource supports OR. + /// + /// The left expression. Will never be null. + /// The right expression. Will never be null. + /// The OR expression. A return value of null means + /// a native OR query cannot be created for the given expressions. + /// In this case, may return multiple queries, each + /// of which must be run and results combined. + protected virtual T CreateOrExpression(T leftExpression, T rightExpression) + { + return null; + } + + /// + /// Should be overridden by subclasses to create a CONTAINS expression + /// if the native resource supports CONTAINS. + /// + /// The contains filter. Will never be null. + /// True if this should be a NOT CONTAINS + /// The CONTAINS expression. A return value of null means + /// a native CONTAINS query cannot be created for the given filter. + /// In this case, may return an empty query set, meaning + /// fetch everything. The filter will be re-applied in memory + /// to the resulting object stream. This does not scale well, so + /// if possible, you should implement this method. + protected virtual T CreateContainsExpression(ContainsFilter filter, bool not) + { + return null; + } + + /// + /// Should be overridden by subclasses to create a ENDS-WITH expression + /// if the native resource supports ENDS-WITH. + /// + /// The contains filter. Will never be null. + /// True if this should be a NOT ENDS-WITH + /// The ENDS-WITH expression. A return value of null means + /// a native ENDS-WITH query cannot be created for the given filter. + /// In this case, may return an empty query set, meaning + /// fetch everything. The filter will be re-applied in memory + /// to the resulting object stream. This does not scale well, so + /// if possible, you should implement this method. + protected virtual T CreateEndsWithExpression(EndsWithFilter filter, bool not) + { + return null; + } + + /// + /// Should be overridden by subclasses to create a EQUALS expression + /// if the native resource supports EQUALS. + /// + /// The contains filter. Will never be null. + /// True if this should be a NOT EQUALS + /// The EQUALS expression. A return value of null means + /// a native EQUALS query cannot be created for the given filter. + /// In this case, may return an empty query set, meaning + /// fetch everything. The filter will be re-applied in memory + /// to the resulting object stream. This does not scale well, so + /// if possible, you should implement this method. + protected virtual T CreateEqualsExpression(EqualsFilter filter, bool not) + { + return null; + } + + /// + /// Should be overridden by subclasses to create a GREATER-THAN expression + /// if the native resource supports GREATER-THAN. + /// + /// The contains filter. Will never be null. + /// True if this should be a NOT GREATER-THAN + /// The GREATER-THAN expression. A return value of null means + /// a native GREATER-THAN query cannot be created for the given filter. + /// In this case, may return an empty query set, meaning + /// fetch everything. The filter will be re-applied in memory + /// to the resulting object stream. This does not scale well, so + /// if possible, you should implement this method. + protected virtual T CreateGreaterThanExpression(GreaterThanFilter filter, bool not) + { + return null; + } + + /// + /// Should be overridden by subclasses to create a GREATER-THAN-EQUAL expression + /// if the native resource supports GREATER-THAN-EQUAL. + /// + /// The contains filter. Will never be null. + /// True if this should be a NOT GREATER-THAN-EQUAL + /// The GREATER-THAN-EQUAL expression. A return value of null means + /// a native GREATER-THAN-EQUAL query cannot be created for the given filter. + /// In this case, may return an empty query set, meaning + /// fetch everything. The filter will be re-applied in memory + /// to the resulting object stream. This does not scale well, so + /// if possible, you should implement this method. + protected virtual T CreateGreaterThanOrEqualExpression(GreaterThanOrEqualFilter filter, bool not) + { + return null; + } + + /// + /// Should be overridden by subclasses to create a LESS-THAN expression + /// if the native resource supports LESS-THAN. + /// + /// The contains filter. Will never be null. + /// True if this should be a NOT LESS-THAN + /// The LESS-THAN expression. A return value of null means + /// a native LESS-THAN query cannot be created for the given filter. + /// In this case, may return an empty query set, meaning + /// fetch everything. The filter will be re-applied in memory + /// to the resulting object stream. This does not scale well, so + /// if possible, you should implement this method. + protected virtual T CreateLessThanExpression(LessThanFilter filter, bool not) + { + return null; + } + + /// + /// Should be overridden by subclasses to create a LESS-THAN-EQUAL expression + /// if the native resource supports LESS-THAN-EQUAL. + /// + /// The contains filter. Will never be null. + /// True if this should be a NOT LESS-THAN-EQUAL + /// The LESS-THAN-EQUAL expression. A return value of null means + /// a native LESS-THAN-EQUAL query cannot be created for the given filter. + /// In this case, may return an empty query set, meaning + /// fetch everything. The filter will be re-applied in memory + /// to the resulting object stream. This does not scale well, so + /// if possible, you should implement this method. + protected virtual T CreateLessThanOrEqualExpression(LessThanOrEqualFilter filter, bool not) + { + return null; + } + + /// + /// Should be overridden by subclasses to create a STARTS-WITH expression + /// if the native resource supports STARTS-WITH. + /// + /// The contains filter. Will never be null. + /// True if this should be a NOT STARTS-WITH + /// The STARTS-WITH expression. A return value of null means + /// a native STARTS-WITH query cannot be created for the given filter. + /// In this case, may return an empty query set, meaning + /// fetch everything. The filter will be re-applied in memory + /// to the resulting object stream. This does not scale well, so + /// if possible, you should implement this method. + protected virtual T CreateStartsWithExpression(StartsWithFilter filter, bool not) + { + return null; + } + + protected virtual T CreateContainsAllValuesExpression(ContainsAllValuesFilter filter, bool not) + { + return null; + } + } + #endregion + + #region AndFilter + public sealed class AndFilter : CompositeFilter + { + + /// + /// Left side of a composite based filter. + /// + private LinkedList _subFilters; + + /// + /// And the the left and right filters. + /// + public AndFilter(Filter left, Filter right) + : this(CollectionUtil.NewList(new[] { left, right })) + { + } + + public AndFilter(IEnumerable filters) + : base(null, null) + { + _subFilters = new LinkedList(filters); + } + + /// + /// Ands the left and right filters. + /// + /// + public override bool Accept(ConnectorObject obj) + { + bool result = true; + foreach (Filter subFilter in _subFilters) + { + result = subFilter.Accept(obj); + if (!result) + { + break; + } + } + return result; + } + + public override TR Accept(FilterVisitor v, TP p) + { + return v.VisitAndFilter(p, this); + } + + public override Filter Left + { + get + { + return _subFilters.First.Value; + } + } + + public override Filter Right + { + get + { + if (_subFilters.Count > 2) + { + var right = new LinkedList(_subFilters); + right.RemoveFirst(); + return new AndFilter(right); + } + else if (_subFilters.Count == 2) + { + return _subFilters.Last.Value; + } + else + { + return null; + } + } + } + + public override ICollection Filters + { + get + { + return CollectionUtil.AsReadOnlyList(new List(_subFilters)); + } + } + + public override string ToString() + { + StringBuilder builder = (new StringBuilder()).Append('('); + bool isFirst = true; + foreach (Filter subFilter in _subFilters) + { + if (isFirst) + { + isFirst = false; + } + else + { + builder.Append(" and "); + } + builder.Append(subFilter); + } + return builder.Append(')').ToString(); + } + } + #endregion + + #region AttributeFilter + public abstract class AttributeFilter : Filter + { + private readonly ConnectorAttribute _attribute; + + /// + /// Root filter for Attribute testing.. + /// + internal AttributeFilter(ConnectorAttribute attribute) + { + _attribute = attribute; + if (attribute == null) + { + throw new ArgumentException("Attribute not be null!"); + } + } + + /// + /// Get the internal attribute. + /// + public ConnectorAttribute GetAttribute() + { + return _attribute; + } + + /// + /// Name of the attribute to find in the . + /// + public string Name + { + get + { + return GetAttribute().Name; + } + } + + /// + /// Determines if the attribute provided is present in the + /// . + /// + public bool IsPresent(ConnectorObject obj) + { + return obj.GetAttributeByName(_attribute.Name) != null; + } + public abstract bool Accept(ConnectorObject obj); + public abstract TR Accept(FilterVisitor v, TP p); + } + #endregion + + #region ComparableAttributeFilter + /// + /// Filter for an attribute value that is comparable. + /// + public abstract class ComparableAttributeFilter : + SingleValueAttributeFilter + { + /// + /// Attempt compare attribute values. + /// + internal ComparableAttributeFilter(ConnectorAttribute attr) + : base(attr) + { + // determine if this attribute value is comparable.. + if (!(GetValue() is IComparable)) + { + String ERR = "Must be a comparable value!"; + throw new ArgumentException(ERR); + } + } + + /// + /// Call compareTo on the attribute values. + /// + /// + /// If the attribute is not present + /// in the return -1. + /// + public int Compare(ConnectorObject obj) + { + int ret = -1; + ConnectorAttribute attr = obj.GetAttributeByName(GetName()); + if (attr != null && attr.Value.Count == 1) + { + // it must be a comparable because that's were testing against + if (!(attr.Value[0] is IComparable)) + { + String ERR = "Attribute value must be comparable!"; + throw new ArgumentException(ERR); + } + // grab this value and the on from the attribute an compare.. + IComparable o1 = (IComparable)attr.Value[0]; + IComparable o2 = (IComparable)GetValue(); + ret = o1.CompareTo(o2); + } + return ret; + } + } + #endregion + + #region CompositeFilter + public abstract class CompositeFilter : Filter + { + /// + /// Left side of a composite based filter. + /// + private Filter _left; + + /// + /// Right side of a composite based filter. + /// + private Filter _right; + + public virtual Filter Left + { + get + { + return _left; + } + } + + public virtual Filter Right + { + get + { + return _right; + } + } + + public virtual ICollection Filters + { + get + { + return CollectionUtil.NewReadOnlyList(Left, Right); + } + } + + internal CompositeFilter(Filter left, Filter right) + { + _left = left; + _right = right; + } + public abstract bool Accept(ConnectorObject obj); + public abstract R Accept(FilterVisitor v, P p); + } + #endregion + + #region ContainsFilter + public sealed class ContainsFilter : StringFilter + { + public ContainsFilter(ConnectorAttribute attr) + : base(attr) + { + } + + public override bool Accept(String value) + { + return value.Contains(GetValue()); + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitContainsFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("CONTAINS: ").Append(GetAttribute()); + return bld.ToString(); + } + } + #endregion + + #region EndsWithFilter + public sealed class EndsWithFilter : StringFilter + { + public EndsWithFilter(ConnectorAttribute attr) + : base(attr) + { + } + + public override bool Accept(String value) + { + return value.EndsWith(GetValue()); + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitEndsWithFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("ENDSWITH: ").Append(GetAttribute()); + return bld.ToString(); + } + } + #endregion + + #region EqualsFilter + public sealed class EqualsFilter : AttributeFilter + { + /// + /// Determines if the attribute inside the is equal + /// to the provided. + /// + public EqualsFilter(ConnectorAttribute attr) + : base(attr) + { + } + + /// + /// Determines if the attribute exists in the and if + /// its equal to the one provided. + /// + /// + public override bool Accept(ConnectorObject obj) + { + bool ret = false; + ConnectorAttribute thisAttr = GetAttribute(); + ConnectorAttribute attr = obj.GetAttributeByName(thisAttr.Name); + if (attr != null) + { + ret = thisAttr.Equals(attr); + } + return ret; + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitEqualsFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("EQUALS: ").Append(GetAttribute()); + return bld.ToString(); + } + + } + #endregion + + #region ExtendedMatchFilter + public sealed class ExtendedMatchFilter : AttributeFilter + { + + /// + /// Creates a new {@code extended match} filter using the provided operator + /// and attribute assertion. + /// + /// + /// The operator. + /// + /// The assertion value. + public ExtendedMatchFilter(string @operator, ConnectorAttribute attribute) + : base(attribute) + { + if (StringUtil.IsBlank(@operator)) + { + throw new ArgumentException("Operator not be null!"); + } + Operator = @operator; + } + + public string Operator + { + get; + internal set; + } + + /// + /// Framework can not understand this filter, always return true. + /// . + /// + public override bool Accept(ConnectorObject obj) + { + return true; + } + + public override TR Accept(FilterVisitor v, TP p) + { + return v.VisitExtendedFilter(p, this); + } + } + #endregion + + #region Filter + public interface Filter + { + /// + /// Determines whether the specified matches this + /// filter. + /// + /// + /// - The specified ConnectorObject. + /// true if the object matches (that is, satisfies all + /// selection criteria of) this filter; otherwise false. + bool Accept(ConnectorObject obj); + + /// + /// Applies a FilterVisitor to this Filter. + /// + /// + /// The return type of the visitor's methods. + /// + /// The type of the additional parameters to the visitor's + /// methods. + /// + /// The filter visitor. + /// + /// Optional additional visitor parameter. + /// A result as specified by the visitor. + /// @since 1.4 + R Accept(FilterVisitor v, P p); + } + #endregion + + #region FilterVisitor + /// + /// A visitor of Filters, in the style of the visitor design + /// pattern. + /// + /// Classes implementing this interface can query filters in a type-safe manner. + /// When a visitor is passed to a filter's accept method, the corresponding visit + /// method most applicable to that filter is invoked. + /// + /// + /// + /// + /// The return type of this visitor's methods. Use + /// for visitors that do not need to return + /// results. + /// + /// The type of the additional parameter to this visitor's methods. + /// Use for visitors that do not need an + /// additional parameter. + /// + /// since 1.4 + public interface FilterVisitor + { + + /// + /// Visits an and filter. + /// + /// Implementation note: for the purposes of matching, an empty + /// sub-filters should always evaluate to true. + /// + /// + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitAndFilter(P p, AndFilter filter); + + /// + /// Visits a contains filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitContainsFilter(P p, ContainsFilter filter); + + /// + /// Visits a containsAll filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitContainsAllValuesFilter(P p, ContainsAllValuesFilter filter); + + /// + /// Visits a equality filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitEqualsFilter(P p, EqualsFilter filter); + + /// + /// Visits a comparison filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitExtendedFilter(P p, Filter filter); + + /// + /// Visits a greater than filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitGreaterThanFilter(P p, GreaterThanFilter filter); + + /// + /// Visits a greater than or equal to filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitGreaterThanOrEqualFilter(P p, GreaterThanOrEqualFilter filter); + + /// + /// Visits a less than filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitLessThanFilter(P p, LessThanFilter filter); + + /// + /// Visits a less than or equal to filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitLessThanOrEqualFilter(P p, LessThanOrEqualFilter filter); + + /// + /// Visits a no filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitNotFilter(P p, NotFilter filter); + + /// + /// Visits an or filter. + /// + /// Implementation note: for the purposes of matching, an empty + /// sub-filters should always evaluate to false. + /// + /// + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitOrFilter(P p, OrFilter filter); + + /// + /// Visits a starts with filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitStartsWithFilter(P p, StartsWithFilter filter); + + /// + /// Visits a ends wit filter. + /// + /// + /// A visitor specified parameter. + /// + /// The visited filter. + /// Returns a visitor specified result. + R VisitEndsWithFilter(P p, EndsWithFilter filter); + } + #endregion + + #region FilterBuilder + /// + /// FilterBuilder creates a object, that can determine if a + /// ConnectorObject will be filtered or not. + /// + /// Will Droste + /// $Revision: 1.7 $ + /// 1.0 + public static class FilterBuilder + { + /// + /// Determine if the value ends + /// with the value provided. + /// + /// + /// value to test against the + /// attribute value. + /// true if the attribute value contains the + /// attribute value provided. + public static Filter EndsWith(ConnectorAttribute attr) + { + return new EndsWithFilter(attr); + } + + /// + /// Determine if the value starts + /// with the value provided. + /// + /// + /// value to test against the + /// attribute value. + /// true if the attribute value contains the + /// attribute value provided. + public static Filter StartsWith(ConnectorAttribute attr) + { + return new StartsWithFilter(attr); + } + + public static Filter ContainsAllValues(ConnectorAttribute attr) + { + return new ContainsAllValuesFilter(attr); + } + + /// + /// Determine if the value contains + /// the value provided. + /// + /// + /// value to test against the + /// attribute value. + /// true if the attribute value contains the + /// attribute value provided. + public static Filter Contains(ConnectorAttribute attr) + { + return new ContainsFilter(attr); + } + + /// + /// The value provided is less than or equal to the + /// attribute value. + /// + /// ConnectorAttribute to do the comparison. + /// true if attribute provided is greater than or equal to the one + /// provided by the . + public static Filter GreaterThanOrEqualTo(ConnectorAttribute attr) + { + return new GreaterThanOrEqualFilter(attr); + } + + /// + /// The value provided is less than or equal to the + /// attribute value. + /// + /// ConnectorAttribute to do the comparison. + /// true if attribute provided is less than or equal to the one + /// provided by the . + public static Filter LessThanOrEqualTo(ConnectorAttribute attr) + { + return new LessThanOrEqualFilter(attr); + } + + /// + /// The value provided is less than the + /// attribute value. + /// + /// ConnectorAttribute to do the comparison. + /// true if attribute provided is less than the one provided by the + /// . + public static Filter LessThan(ConnectorAttribute attr) + { + return new LessThanFilter(attr); + } + + /// + /// ConnectorAttribute value is greater than the attribute + /// value. + /// + /// ConnectorAttribute to do the comparison. + /// true if attribute provided is greater than the one provided by + /// the . + public static Filter GreaterThan(ConnectorAttribute attr) + { + return new GreaterThanFilter(attr); + } + + /// + /// Determines if the provided exists in the + /// and is equal. + /// + public static Filter EqualTo(ConnectorAttribute attr) + { + return new EqualsFilter(attr); + } + + /// + /// Creates a new {@code extended match} filter using the provided operator + /// and attribute assertion. + /// + /// + /// The operator. + /// + /// The assertion value. + /// Since 1.5 + public static Filter ExtendedMatch(string @operator, ConnectorAttribute attribute) + { + return new ExtendedMatchFilter(@operator, attribute); + } + + /// + /// Ands the two . + /// + /// left side operand. + /// right side operand. + /// the result of leftOperand && rightOperand + public static Filter And(Filter leftOperand, Filter rightOperand) + { + return new AndFilter(leftOperand, rightOperand); + } + + /// + /// Creates a new "AND" filter using the provided list of sub-filters. + /// + /// Creating a new "AND" filter with a {@code null} or empty list of + /// sub-filters is equivalent to calling "alwaysTrue". + /// + /// + /// + /// + /// The list of sub-filters, may be empty or {@code null}. + /// The newly created "AND" filter. + public static Filter And(ICollection subFilters) + { + switch (subFilters.Count) + { + case 0: + return null; + case 1: + return subFilters.First(); + default: + return new AndFilter(new List(subFilters)); + } + } + + /// + /// ORs the two . + /// + /// left side operand. + /// right side operand. + /// the result of leftOperand || rightOperand + public static Filter Or(Filter leftOperand, Filter rightOperand) + { + return new OrFilter(leftOperand, rightOperand); + } + + /// + /// Creates a new "OR" filter using the provided list of sub-filters. + /// + /// Creating a new "OR" filter with a {@code null} or empty list of + /// sub-filters is equivalent to "alwaysTrue". + /// + /// + /// + /// + /// The list of sub-filters, may be empty or {@code null}. + /// The newly created {@code or} filter. + public static Filter Or(ICollection subFilters) + { + switch (subFilters.Count) + { + case 0: + return null; + case 1: + return subFilters.First(); + default: + return new OrFilter(new List(subFilters)); + } + } + + /// + /// NOT the . + /// + /// negate the result of . + /// the result of not . + public static Filter Not(Filter filter) + { + return new NotFilter(filter); + } + + /// + /// Creates a new presence filter using the provided attribute name. + /// + /// + /// The name of field within the which must be + /// present. + /// The newly created {@code presence} filter. + /// Since 1.5 + public static Filter Present(string attributeName) + { + return new PresenceFilter(attributeName); + } + } + #endregion + + #region FilteredResultsHandlerVisitor + + public enum FilterResult + { + False, + True, + Undefined + } + + static class EnumExtensionMethods + { + public static bool ToBoolean(this FilterResult instance) + { + return instance == FilterResult.True; // UNDEFINED collapses to FALSE. + } + + } + + /// + /// A FilteredResultsHandlerVisitor can accept the + /// . It + /// can be used for case-sensitive and case-ignore mode to accept the + /// and values. + /// + /// + /// since 1.5 + public class FilteredResultsHandlerVisitor : FilterVisitor + { + + public static readonly FilteredResultsHandlerVisitor DefaultCaseSensitiveVisitor = new FilteredResultsHandlerVisitor(false); + public static readonly FilteredResultsHandlerVisitor DefaultCaseIgnoreVisitor = new FilteredResultsHandlerVisitor(true); + + private FilterResult valueOf(bool b) + { + return b ? FilterResult.True : FilterResult.False; + } + + public static Filter WrapFilter(Filter nestedFilter, bool caseIgnore) + { + return null == nestedFilter ? null : new InnerVisitorFilter(nestedFilter, caseIgnore); + } + + private sealed class InnerVisitorFilter : Filter + { + private readonly Filter _nestedFilter; + private readonly bool _caseIgnore; + + public InnerVisitorFilter(Filter nestedFilter, bool caseIgnore) + { + _nestedFilter = nestedFilter; + _caseIgnore = caseIgnore; + } + + public bool Accept(ConnectorObject obj) + { + return _nestedFilter.Accept(_caseIgnore ? DefaultCaseIgnoreVisitor : DefaultCaseSensitiveVisitor, obj).ToBoolean(); + } + + public TR Accept(FilterVisitor v, TP p) + { + return v.VisitExtendedFilter(p, this); + } + } + + private readonly bool _caseIgnore; + + public FilteredResultsHandlerVisitor(bool caseIgnore) + { + _caseIgnore = caseIgnore; + } + + public virtual FilterResult VisitAndFilter(ConnectorObject connectorObject, AndFilter filter) + { + FilterResult result = FilterResult.True; + foreach (Filter subFilter in filter.Filters) + { + FilterResult r = subFilter.Accept(this, connectorObject); + if (r.CompareTo(result) < 0) + { + result = r; + } + if (result == FilterResult.False) + { + break; + } + } + return result; + } + + public virtual FilterResult VisitContainsFilter(ConnectorObject connectorObject, ContainsFilter filter) + { + FilterResult result = FilterResult.Undefined; + String valueAssertion = ExpectSingleValue(connectorObject, filter.Name, typeof(String)); + if (null != valueAssertion) + { + if (_caseIgnore) + { + result = valueOf(valueAssertion.ToLower(Thread.CurrentThread.CurrentUICulture).Contains(filter.GetValue().ToLower(Thread.CurrentThread.CurrentUICulture))); + } + else + { + result = valueOf(valueAssertion.Contains(filter.GetValue())); + } + } + return result; + } + + public virtual FilterResult VisitContainsAllValuesFilter(ConnectorObject connectorObject, ContainsAllValuesFilter filter) + { + FilterResult result = FilterResult.Undefined; + ConnectorAttribute attribute = connectorObject.GetAttributeByName(filter.Name); + IList attributeValues = null; + if (null != attribute && null != (attributeValues = attribute.Value)) + { + IList filterValues = filter.GetAttribute().Value; + if (filterValues.Count == 0) + { + result = FilterResult.True; + } + else if (attributeValues.Count == 0) + { + result = FilterResult.False; + } + else if (_caseIgnore) + { + bool stillContains = true; + foreach (object o in filter.GetAttribute().Value) + { + bool found = false; + if (o is string) + { + foreach (object c in attributeValues) + { + if (c is string && ((string)c).Equals((string)o, StringComparison.CurrentCultureIgnoreCase)) + { + found = true; + break; + } + } + } + else if (o is char) + { + foreach (object c in attributeValues) + { + if (c is char && char.ToUpper((char)c) != char.ToUpper((char)o)) + { + found = true; + break; + } + } + } + else + { + result = valueOf(filter.GetAttribute().Value.Except(attributeValues).Any()); + break; + } + if (!(stillContains = stillContains && found)) + { + break; + } + } + result = valueOf(stillContains); + } + else + { + result = valueOf(filter.GetAttribute().Value.Except(attributeValues).Any()); + } + } + return result; + } + public virtual FilterResult VisitEqualsFilter(ConnectorObject connectorObject, EqualsFilter filter) + { + FilterResult result = FilterResult.Undefined; + ConnectorAttribute attribute = connectorObject.GetAttributeByName(filter.Name); + if (null != attribute) + { + IList attributeValues = attribute.Value; + IList filterValues = filter.GetAttribute().Value; + result = valueOf(CollectionUtil.Equals(attributeValues, filterValues, _caseIgnore)); + } + return result; + } + public virtual FilterResult VisitExtendedFilter(ConnectorObject connectorObject, Filter filter) + { + FilterResult result = FilterResult.Undefined; + if (filter is PresenceFilter) + { + result = valueOf(null != connectorObject.GetAttributeByName(((PresenceFilter)filter).Name)); + } + return result; + } + public virtual FilterResult VisitGreaterThanFilter(ConnectorObject connectorObject, GreaterThanFilter filter) + { + FilterResult result = FilterResult.Undefined; + Object valueAssertion = ExpectSingleValue(connectorObject, filter.Name); + if (null != valueAssertion) + { + if (!(valueAssertion is IComparable)) + { + throw new ArgumentException("Attribute value " + filter.Name + " must be comparable! Found" + valueAssertion.GetType()); + } + object filterValue = filter.GetValue(); + if (_caseIgnore && filterValue is String) + { + var s = valueAssertion as string; + if (s != null) + { + result = valueOf(String.Compare(s, (string)filterValue, StringComparison.OrdinalIgnoreCase) > 0); + } + } + else if (_caseIgnore && filterValue is char) + { + var c = valueAssertion as char?; + if (c != null) + { + result = valueOf((Char.ToLower((char)c)) - (Char.ToLower((char)filterValue)) > 0); + } + } + else + { + result = valueOf(CollectionUtil.ForceCompare(valueAssertion, filterValue) > 0); + } + } + return result; + } + + + public virtual FilterResult VisitGreaterThanOrEqualFilter(ConnectorObject connectorObject, GreaterThanOrEqualFilter filter) + { + FilterResult result = FilterResult.Undefined; + object valueAssertion = ExpectSingleValue(connectorObject, filter.Name); + if (null != valueAssertion) + { + if (!(valueAssertion is IComparable)) + { + throw new ArgumentException("Attribute value " + filter.Name + " must be comparable! Found" + valueAssertion.GetType()); + } + object filterValue = filter.GetValue(); + if (_caseIgnore && filterValue is string) + { + var s = valueAssertion as string; + if (s != null) + { + result = valueOf(String.Compare(s, (string)filterValue, StringComparison.OrdinalIgnoreCase) >= 0); + } + } + else if (_caseIgnore && filterValue is char) + { + var c = valueAssertion as char?; + if (c != null) + { + result = valueOf((Char.ToLower((char)c)) - (Char.ToLower((char)filterValue)) >= 0); + } + } + else + { + result = valueOf(CollectionUtil.ForceCompare(valueAssertion, filterValue) >= 0); + } + } + return result; + } + public virtual FilterResult VisitLessThanFilter(ConnectorObject connectorObject, LessThanFilter filter) + { + FilterResult result = FilterResult.Undefined; + object valueAssertion = ExpectSingleValue(connectorObject, filter.Name); + if (null != valueAssertion) + { + if (!(valueAssertion is IComparable)) + { + throw new ArgumentException("Attribute value " + filter.Name + " must be comparable! Found" + valueAssertion.GetType()); + } + object filterValue = filter.GetValue(); + if (_caseIgnore && filterValue is string) + { + var s = valueAssertion as string; + if (s != null) + { + result = valueOf(String.Compare(s, (string)filterValue, StringComparison.OrdinalIgnoreCase) < 0); + } + } + else if (_caseIgnore && filterValue is char?) + { + var c = valueAssertion as char?; + if (c != null) + { + result = valueOf((Char.ToLower((char)c)) - (Char.ToLower((char)filterValue)) < 0); + } + } + else + { + result = valueOf(CollectionUtil.ForceCompare(valueAssertion, filterValue) < 0); + } + } + return result; + } + public virtual FilterResult VisitLessThanOrEqualFilter(ConnectorObject connectorObject, LessThanOrEqualFilter filter) + { + FilterResult result = FilterResult.Undefined; + object valueAssertion = ExpectSingleValue(connectorObject, filter.Name); + if (null != valueAssertion) + { + if (!(valueAssertion is IComparable)) + { + throw new ArgumentException("Attribute value " + filter.Name + " must be comparable! Found" + valueAssertion.GetType()); + } + object filterValue = filter.GetValue(); + if (_caseIgnore && filterValue is string) + { + var s = valueAssertion as string; + if (s != null) + { + result = valueOf(String.Compare(s, (string)filterValue, StringComparison.OrdinalIgnoreCase) <= 0); + } + } + else if (_caseIgnore && filterValue is char?) + { + var c = valueAssertion as char?; + if (c != null) + { + result = valueOf((Char.ToLower((char)c)) - (Char.ToLower((char)filterValue)) <= 0); + } + } + else + { + result = valueOf(CollectionUtil.ForceCompare(valueAssertion, filterValue) <= 0); + } + } + return result; + } + public virtual FilterResult VisitNotFilter(ConnectorObject connectorObject, NotFilter filter) + { + switch (filter.Filter.Accept(this, connectorObject)) + { + case FilterResult.False: + return FilterResult.True; + case FilterResult.Undefined: + return FilterResult.Undefined; + default: // TRUE + return FilterResult.False; + } + } + + public virtual FilterResult VisitOrFilter(ConnectorObject connectorObject, OrFilter filter) + { + FilterResult result = FilterResult.False; + foreach (Filter subFilter in filter.Filters) + { + FilterResult r = subFilter.Accept(this, connectorObject); + if (r.CompareTo(result) > 0) + { + result = r; + } + if (result == FilterResult.True) + { + break; + } + } + return result; + } + + public virtual FilterResult VisitStartsWithFilter(ConnectorObject connectorObject, StartsWithFilter filter) + { + FilterResult result = FilterResult.Undefined; + String valueAssertion = ExpectSingleValue(connectorObject, filter.Name, typeof(String)); + if (null != valueAssertion) + { + if (_caseIgnore) + { + result = valueOf(valueAssertion.ToLower(Thread.CurrentThread.CurrentUICulture).StartsWith(filter.GetValue().ToLower(Thread.CurrentThread.CurrentUICulture))); + } + else + { + result = valueOf(valueAssertion.StartsWith(filter.GetValue())); + } + } + return result; + } + + public virtual FilterResult VisitEndsWithFilter(ConnectorObject connectorObject, EndsWithFilter filter) + { + FilterResult result = FilterResult.Undefined; + String valueAssertion = ExpectSingleValue(connectorObject, filter.Name, typeof(String)); + if (null != valueAssertion) + { + if (_caseIgnore) + { + result = valueOf(valueAssertion.ToLower(Thread.CurrentThread.CurrentUICulture).EndsWith(filter.GetValue().ToLower(Thread.CurrentThread.CurrentUICulture))); + } + else + { + result = valueOf(valueAssertion.EndsWith(filter.GetValue())); + } + } + return result; + } + + protected internal virtual T ExpectSingleValue(ConnectorObject connectorObject, string attributeName, Type expect) + { + object o = ExpectSingleValue(connectorObject, attributeName); + if (null != o && expect.IsInstanceOfType(o)) + { + return (T)o; + } + return default(T); + } + + protected internal virtual object ExpectSingleValue(ConnectorObject connectorObject, string attributeName) + { + ConnectorAttribute attr = connectorObject.GetAttributeByName(attributeName); + if (null != attr && null != attr.Value && attr.Value.Count() == 1) + { + return attr.Value[0]; + } + return null; + } + + } + #endregion + + #region FilterTranslator + public interface FilterTranslator + { + IList Translate(Filter filter); + } + #endregion + + #region GreaterThanFilter + public sealed class GreaterThanFilter : ComparableAttributeFilter + { + /// + /// Determine if the value is + /// greater than the one provided in the filter. + /// + public GreaterThanFilter(ConnectorAttribute attr) + : base(attr) + { + } + + /// + /// Determine if the value is + /// greater than the one provided in the filter. + /// + /// + public override bool Accept(ConnectorObject obj) + { + return IsPresent(obj) && Compare(obj) > 0; + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitGreaterThanFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("GREATERTHAN: ").Append(GetAttribute()); + return bld.ToString(); + } + } + #endregion + + #region GreaterThanOrEqualFilter + public sealed class GreaterThanOrEqualFilter : ComparableAttributeFilter + { + /// + /// Determine if the value is + /// greater than the one provided in the filter. + /// + public GreaterThanOrEqualFilter(ConnectorAttribute attr) + : base(attr) + { + } + + /// + /// Determine if the value is + /// greater than the one provided in the filter. + /// + /// + public override bool Accept(ConnectorObject obj) + { + return IsPresent(obj) && Compare(obj) >= 0; + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitGreaterThanOrEqualFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("GREATERTHANOREQUAL: ").Append(GetAttribute()); + return bld.ToString(); + } + } + #endregion + + #region LessThanFilter + public sealed class LessThanFilter : ComparableAttributeFilter + { + /// + /// Determine if the value is + /// greater than the one provided in the filter. + /// + public LessThanFilter(ConnectorAttribute attr) + : base(attr) + { + } + + /// + /// Determine if the value is + /// greater than the one provided in the filter. + /// + /// + public override bool Accept(ConnectorObject obj) + { + return IsPresent(obj) && this.Compare(obj) < 0; + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitLessThanFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("LESSTHAN: ").Append(GetAttribute()); + return bld.ToString(); + } + } + #endregion + + #region LessThanOrEqualFilter + public sealed class LessThanOrEqualFilter : ComparableAttributeFilter + { + /// + /// Determine if the value is + /// greater than the one provided in the filter. + /// + public LessThanOrEqualFilter(ConnectorAttribute attr) + : base(attr) + { + } + + /// + /// Determine if the value is + /// greater than the one provided in the filter. + /// + /// + public override bool Accept(ConnectorObject obj) + { + return IsPresent(obj) && this.Compare(obj) <= 0; + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitLessThanOrEqualFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("LESSTHANOREQUAL: ").Append(GetAttribute()); + return bld.ToString(); + } + } + #endregion + + #region NotFilter + /// + /// Proxy the filter to return the negative of the value. + /// + public sealed class NotFilter : Filter + { + private readonly Filter _filter; + + /// + /// Take the value returned from the internal filter and NOT it. + /// + public NotFilter(Filter filter) + { + _filter = filter; + } + + /// + /// Get the internal filter that is being negated. + /// + public Filter Filter + { + get + { + return _filter; + } + } + + /// + /// Return the opposite the internal filters return value. + /// + /// + public bool Accept(ConnectorObject obj) + { + return !_filter.Accept(obj); + } + + public R Accept(FilterVisitor v, P p) + { + return v.VisitNotFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("NOT: ").Append(Filter); + return bld.ToString(); + } + } + #endregion + + #region OrFilter + public sealed class OrFilter : CompositeFilter + { + + /// + /// Left side of a composite based filter. + /// + private LinkedList _subFilters; + + /// + /// Or the the left and right filters. + /// + public OrFilter(Filter left, Filter right) + : this(CollectionUtil.NewList(new[] { left, right })) + { + } + + public OrFilter(IEnumerable filters) + : base(null, null) + { + _subFilters = new LinkedList(filters); + } + + /// + /// ORs the left and right filters. + /// + /// + public override bool Accept(ConnectorObject obj) + { + bool result = false; + foreach (Filter subFilter in _subFilters) + { + result = subFilter.Accept(obj); + if (result) + { + break; + } + } + return result; + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitOrFilter(p, this); + } + + public override Filter Left + { + get + { + return _subFilters.First.Value; + } + } + + public override Filter Right + { + get + { + if (_subFilters.Count > 2) + { + var right = new LinkedList(_subFilters); + right.RemoveFirst(); + return new AndFilter(right); + } + if (_subFilters.Count == 2) + { + return _subFilters.Last.Value; + } + return null; + } + } + + public override ICollection Filters + { + get + { + return CollectionUtil.AsReadOnlyList(new List(_subFilters)); + } + } + + public override string ToString() + { + StringBuilder builder = (new StringBuilder()).Append('('); + bool isFirst = true; + foreach (Filter subFilter in _subFilters) + { + if (isFirst) + { + isFirst = false; + } + else + { + builder.Append(" or "); + } + builder.Append(subFilter); + } + return builder.Append(')').ToString(); + } + } + #endregion + + #region PresenceFilter + /// + /// A PresenceFilter determines if the attribute provided is present in the + /// + /// + /// since 1.5 + public class PresenceFilter : Filter + { + + private readonly string _name; + + public PresenceFilter(string attributeName) + { + this._name = attributeName; + if (StringUtil.IsBlank(_name)) + { + throw new ArgumentException("Attribute name not be null!"); + } + } + + /// + /// Name of the attribute to find in the . + /// + public virtual string Name + { + get + { + return _name; + } + } + + /// + /// Determines if the attribute provided is present in the + /// . + /// + public virtual bool Accept(ConnectorObject obj) + { + return obj.GetAttributeByName(_name) != null; + } + + public virtual R Accept(FilterVisitor v, P p) + { + return v.VisitExtendedFilter(p, this); + } + } + #endregion + + #region SingleValueAttributeFilter + /// + /// Get a single value out of the attribute to test w/. + /// + public abstract class SingleValueAttributeFilter : AttributeFilter + { + /// + /// Attempt to single out the value for comparison. + /// + internal SingleValueAttributeFilter(ConnectorAttribute attr) : + base(attr) + { + // make sure this is not a Uid.. + if (Uid.NAME.Equals(attr.Name)) + { + String MSG = "Uid can only be used for equals comparison."; + throw new ArgumentException(MSG); + } + // actual runtime.. + if (attr.Value.Count != 1) + { + String ERR = "Must only be one value!"; + throw new ArgumentException(ERR); + } + } + + /// + /// Value to test against. + /// + public Object GetValue() + { + return GetAttribute().Value[0]; + } + /// + /// Name of the attribute to find in the . + /// + public String GetName() + { + return GetAttribute().Name; + } + } + #endregion + + #region StartsWithFilter + public sealed class StartsWithFilter : StringFilter + { + public StartsWithFilter(ConnectorAttribute attr) + : base(attr) + { + } + + public override bool Accept(String value) + { + return value.StartsWith(GetValue()); + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitStartsWithFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("STARTSWITH: ").Append(GetAttribute()); + return bld.ToString(); + } + } + #endregion + + #region StringFilter + /// + /// Filter based on strings. + /// + public abstract class StringFilter : SingleValueAttributeFilter + { + /// + /// Attempts to get a string from the attribute. + /// + internal StringFilter(ConnectorAttribute attr) + : base(attr) + { + Object val = base.GetValue(); + if (!(val is string)) + { + String MSG = "Value must be a string!"; + throw new ArgumentException(MSG); + } + } + + /// + /// Get the string value from the afore mentioned attribute. + /// + /// + public new String GetValue() + { + return (String)base.GetValue(); + } + + /// + /// + /// iff the value from the 's attribute + /// of the same name as provided is not a string. + /// + public override bool Accept(ConnectorObject obj) + { + bool ret = false; + ConnectorAttribute attr = obj.GetAttributeByName(GetName()); + if (attr != null) + { + ret = Accept((string)attr.Value[0]); + } + return ret; + } + + public abstract bool Accept(String value); + } + #endregion + + #region ContainsAllValues Filter + public class ContainsAllValuesFilter : AttributeFilter + { + private readonly string _name; + private readonly IList _values; + + public ContainsAllValuesFilter(ConnectorAttribute attr) + : base(attr) + { + _name = attr.Name; + _values = attr.Value; + } + /// + /// Determine if the contains an + /// which contains all the values provided in the passed + /// into the filter. + /// + public override bool Accept(ConnectorObject obj) + { + bool rv = false; + ConnectorAttribute found = obj.GetAttributeByName(_name); + if (found != null) + { + // TODO: possible optimization using 'Set' + foreach (object o in _values) + { + if (!(rv = found.Value.Contains(o))) + { + break; + } + } + } + return rv; + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitContainsAllValuesFilter(p, this); + } + } + #endregion + + #region ExternallyChainedFilter + /// + /// Externally chained filters e.g. the filter implementing case insensitive searches. + /// They are removed from translation. + /// + /// IMPORTANT: Currently, these filters have to be chained at the beginning of the filter tree. + /// + /// + public abstract class ExternallyChainedFilter : Filter + { + private readonly Filter _filter; + + /// + /// Get the internal filter that is being chained upon. + /// + public Filter Filter + { + get + { + return _filter; + } + } + + protected ExternallyChainedFilter(Filter filter) + { + _filter = filter; + } + + public abstract bool Accept(ConnectorObject obj); + public abstract R Accept(FilterVisitor v, P p); + } + + #endregion + + +} \ No newline at end of file diff --git a/dotnet/framework/Framework/CommonSerializer.cs b/dotnet/framework/Framework/CommonSerializer.cs new file mode 100644 index 00000000..4b8b5dc4 --- /dev/null +++ b/dotnet/framework/Framework/CommonSerializer.cs @@ -0,0 +1,330 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012 ForgeRock AS. + */ +using System; +using System.IO; +using System.Collections.Generic; +using Org.IdentityConnectors.Common; +namespace Org.IdentityConnectors.Framework.Common.Serializer +{ + /// + /// Interface for reading objects from a stream. + /// + public interface BinaryObjectDeserializer + { + /// + /// Reads the next object from the stream. + /// + /// + /// Throws + /// a wrapped if end of stream is reached. + /// + /// The next object from the stream. + object ReadObject(); + + /// + /// Closes the underlying stream + /// + void Close(); + } + /// + /// Interface for writing objects to a stream. + /// + public interface BinaryObjectSerializer + { + /// + /// Writes the next object to the stream. + /// + /// The object to write. + /// + void WriteObject(object obj); + + /// + /// Flushes the underlying stream. + /// + void Flush(); + + /// + /// Closes the underylying stream after first flushing it. + /// + void Close(); + } + + /// + /// Serializer factory for serializing connector objects. + /// + /// + /// The list of + /// supported types are as follows: + /// TODO: list supported types + /// + /// + /// + /// + public abstract class ObjectSerializerFactory + { + // At some point we might make this pluggable, but for now, hard-code + private const string IMPL_NAME = "Org.IdentityConnectors.Framework.Impl.Serializer.ObjectSerializerFactoryImpl"; + + private static ObjectSerializerFactory _instance; + + private static readonly Object LOCK = new Object(); + + + /// + /// Get the singleton instance of the . + /// + public static ObjectSerializerFactory GetInstance() + { + lock (LOCK) + { + if (_instance == null) + { + SafeType t = + FrameworkInternalBridge.LoadType(IMPL_NAME); + + _instance = t.CreateInstance(); + } + return _instance; + } + } + + /// + /// Creates a BinaryObjectSerializer for writing objects to + /// the given stream. + /// + /// + /// NOTE: consider using + /// for convenience serializing a single object. + /// + /// NOTE2: do not mix and match + /// with {. This is unsafe since there + /// is header information and state associated with the stream. Objects written + /// using one method must be read using the proper corresponding method. + /// + /// The stream + /// The serializer + public abstract BinaryObjectSerializer NewBinarySerializer(Stream os); + + /// + /// Creates a BinaryObjectDeserializer for reading objects from + /// the given stream. + /// + /// + /// NOTE: consider using + /// for convenience deserializing a single object. + /// NOTE2: do not mix and match + /// with {. This is unsafe since there + /// is header information and state associated with the stream. Objects written + /// using one method must be read using the proper corresponding method. + /// + /// The stream + /// The deserializer + public abstract BinaryObjectDeserializer NewBinaryDeserializer(Stream i); + + /// + /// Creates a BinaryObjectSerializer for writing objects to + /// the given stream. + /// + /// + /// NOTE: consider using + /// for convenience serializing a single object. + /// + /// NOTE2: do not mix and match + /// with {. + /// + /// The writer + /// True to include the xml header + /// Is this to produce a multi-object document. If false, only + /// a single object may be written. + /// The serializer + public abstract XmlObjectSerializer NewXmlSerializer(TextWriter w, + bool includeHeader, + bool multiObject); + + /// + /// Deserializes XML objects from a stream + /// NOTE: Consider using + /// for convenience deserializing a single object. + /// + /// + /// NOTE2: Do not mix and match + /// with {. + /// + /// The input source + /// The callback to receive objects from the stream + /// True iff we are to validate + public abstract void DeserializeXmlStream(TextReader reader, + XmlObjectResultsHandler handler, + bool validate); + + } + + /// + /// Bag of utilities for serialization + /// + public static class SerializerUtil + { + /// + /// Serializes the given object to bytes + /// + /// The object to serialize + /// The bytes + /// + public static byte[] SerializeBinaryObject(object obj) + { + ObjectSerializerFactory fact = ObjectSerializerFactory.GetInstance(); + MemoryStream mem = new MemoryStream(); + BinaryObjectSerializer ser = fact.NewBinarySerializer(mem); + ser.WriteObject(obj); + ser.Close(); + return mem.ToArray(); + } + + /// + /// Serializes the given object to Base64 string + /// + /// The object to serialize + /// The Base64 string + /// + /// Since 1.4 + public static string SerializeBase64Object(object obj) + { + return Convert.ToBase64String(SerializeBinaryObject(obj)); + } + + /// + /// Deserializes the given object from bytes + /// + /// The bytes to deserialize + /// The object + /// + public static object DeserializeBinaryObject(byte[] bytes) + { + ObjectSerializerFactory fact = ObjectSerializerFactory.GetInstance(); + MemoryStream mem = new MemoryStream(bytes); + BinaryObjectDeserializer des = fact.NewBinaryDeserializer(mem); + return des.ReadObject(); + } + + /// + /// Deserializes the given object from Base64 string + /// + /// The string to deserialize + /// The object + /// Since 1.4 + public static object DeserializeBase64Object(string encdata) + { + return DeserializeBinaryObject(Convert.FromBase64String(encdata)); + } + + + /// + /// Serializes the given object to xml + /// + /// The object to serialize + /// True if we are to include the xml header. + /// The xml + /// + public static String SerializeXmlObject(Object obj, bool includeHeader) + { + ObjectSerializerFactory fact = ObjectSerializerFactory.GetInstance(); + StringWriter w = new StringWriter(); + XmlObjectSerializer ser = fact.NewXmlSerializer(w, includeHeader, false); + ser.WriteObject(obj); + ser.Close(true); + return w.ToString(); + } + + /// + /// Deserializes the given object from xml + /// + /// The xml to deserialize + /// True if we are to validate the xml + /// The object + /// + public static Object DeserializeXmlObject(String str, bool validate) + { + ObjectSerializerFactory fact = ObjectSerializerFactory.GetInstance(); + StringReader source = new StringReader(str); + IList rv = new List(); + fact.DeserializeXmlStream(source, + obj => + { + rv.Add(obj); + return true; + }, validate); + if (rv.Count > 0) + { + return rv[0]; + } + else + { + return null; + } + } + + /// + /// Clones the given object by serializing it to bytes and then + /// deserializing it. + /// + /// The object. + /// A clone of the object + public static object CloneObject(Object obj) + { + byte[] bytes = SerializeBinaryObject(obj); + return DeserializeBinaryObject(bytes); + } + + } + + /// + /// Callback interface to receive xml objects from a stream of objects. + /// + public delegate bool XmlObjectResultsHandler(Object obj); + + /// + /// Interface for writing objects to a stream. + /// + public interface XmlObjectSerializer + { + /// + /// Writes the next object to the stream. + /// + /// The object to write. + /// + /// if there is more than one object + /// and this is not configured for multi-object document. + void WriteObject(Object obj); + + /// + /// Flushes the underlying stream. + /// + void Flush(); + + /// + /// Adds document end tag and optinally closes the underlying stream + /// + void Close(bool closeUnderlyingStream); + } +} \ No newline at end of file diff --git a/dotnet/framework/Framework/Framework.csproj b/dotnet/framework/Framework/Framework.csproj new file mode 100644 index 00000000..0fcd0114 --- /dev/null +++ b/dotnet/framework/Framework/Framework.csproj @@ -0,0 +1,106 @@ + + + + + {8B24461B-456A-4032-89A1-CD418F7B5B62} + Debug + AnyCPU + Library + Org.IdentityConnectors.Framework + Framework + Open Connectors Framework + v4.5.2 + False + False + 4 + false + true + + + + prompt + 4 + AnyCPU + bin\Debug\ + true + Full + False + True + DEBUG;TRACE + + + pdbonly + bin\Release\ + true + prompt + 4 + True + False + TRACE + + + False + Auto + 4194304 + AnyCPU + 4096 + + + false + + + false + + + + + + 4.0 + + + + + + + + + + + + + + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/Framework/Spi.cs b/dotnet/framework/Framework/Spi.cs new file mode 100644 index 00000000..dfaab7ea --- /dev/null +++ b/dotnet/framework/Framework/Spi.cs @@ -0,0 +1,547 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Spi.Operations; +namespace Org.IdentityConnectors.Framework.Spi +{ + #region AttributeNormalizer + /// + /// Interface to be implemented by connectors that need + /// to normalize certain attributes. + /// + /// + /// This might, for + /// example, be used to normalize whitespace within + /// DN's to ensure consistent filtering whether that + /// filtering is natively on the resource or by the + /// connector framework. For connectors implementing + /// this interface, the method + /// will be applied to each of the following: + /// + /// + /// The filter passed to . + /// + /// + /// + /// The results returned from . + /// + /// + /// + /// The results returned from . + /// + /// + /// + /// The attributes passed to . + /// + /// + /// + /// The Uid returned from . + /// + /// + /// + /// The attributes passed to . + /// + /// + /// + /// The Uid returned from . + /// + /// + /// + /// The attributes passed to . + /// + /// + /// + /// The Uid returned from . + /// + /// + /// + /// The Uid passed to . + /// + /// + /// + /// + public interface AttributeNormalizer + { + ConnectorAttribute NormalizeAttribute(ObjectClass oclass, ConnectorAttribute attribute); + } + #endregion + + #region Configuration + /// + /// Encapsulates the configuration of a connector. + /// + /// Implementations of the Configuration interface must have a default + /// constructor. All properties are considered configuration for the connector. + /// The initial value of the property is + /// considered the default value of the property. The types of the properties + /// can be only those returned by and + /// multi-dimensional arrays thereof. The properties are not required by default, + /// but a property can be marked as required through use of the . + /// + /// + /// Each property corresponds to two entries in a resource named Messages: + /// display_[Property] and help_[Property]. For example, + /// help_hostname and display_helphostname would be the keys + /// corresponding to a hostname property. The display message is the display + /// name of the property and can be used to display the property in a view. The help + /// message holds the description of the property. The names of the two keys can be overridden + /// through the ConfigurationProperty annotation. + /// + /// + public interface Configuration + { + /// + /// Gets or sets the {@link ConnectorMessages message catalog} instance that allows the Connector + /// to localize messages. The setter is called before any bean property setter, + /// the method or this property getter. + /// + ConnectorMessages ConnectorMessages { get; set; } + + /// + /// Determines if the configuration is valid. + /// + /// A valid configuration is one that is ready to be used by the connector: + /// it is complete (all the required properties have been given values) + /// and the property values are well-formed (are in the expected range, + /// have the expected format, etc.) + /// + /// + /// Implementations of this method should not connect to the resource + /// in an attempt to validate the configuration. For example, implementations + /// should not attempt to check that a host of the specified name exists + /// by making a connection to it. Such checks can be performed in the implementation + /// of the method. + /// + /// + /// iff the configuration is not valid. Implementations + /// are encouraged to throw the most specific exception available. + /// When no specific exception is available, implementations can throw + /// . + void Validate(); + } + #endregion + + #region AbstractConfiguration + public abstract class AbstractConfiguration : Configuration + { + public interface IConfigurationChangeCallback + { + void NotifyUpdate(); + } + + private IConfigurationChangeCallback _callback; + + public ConnectorMessages ConnectorMessages { get; set; } + + public abstract void Validate(); + + public void AddChangeCallback(IConfigurationChangeCallback handler) + { + if (null != _callback) + { + throw new InvalidOperationException("Configuration change update handler has been set"); + } + _callback = handler; + } + + protected internal virtual void NotifyConfigurationUpdate() + { + if (null != _callback) + { + try + { + _callback.NotifyUpdate(); + } + catch (Exception) + { + // Ignored + } + } + } + } + #endregion + + #region ConnectorClassAttribute + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ConnectorClassAttribute : System.Attribute + { + + private readonly String _connectorDisplayNameKey; + private readonly String _connectorCategoryKey; + private readonly SafeType _connectorConfigurationClass; + + public ConnectorClassAttribute(String connectorDisplayNameKey, + Type connectorConfigurationClass) + { + _connectorDisplayNameKey = connectorDisplayNameKey; + _connectorCategoryKey = string.Empty; + _connectorConfigurationClass = SafeType.ForRawType(connectorConfigurationClass); + } + + public ConnectorClassAttribute(String connectorDisplayNameKey, String connectorCategoryKey, + Type connectorConfigurationClass) + { + _connectorDisplayNameKey = connectorDisplayNameKey; + _connectorCategoryKey = connectorCategoryKey; + _connectorConfigurationClass = SafeType.ForRawType(connectorConfigurationClass); + } + + /// + /// The display name key. This must be a key in the message catalog. + /// + public string ConnectorDisplayNameKey + { + get + { + return _connectorDisplayNameKey; + } + } + + /// + /// Category the connector belongs to such as 'LDAP' or 'DB'. + /// + public string ConnectorCategoryKey + { + get + { + return _connectorCategoryKey; + } + } + + public SafeType ConnectorConfigurationType + { + get + { + return _connectorConfigurationClass; + } + } + + /// + /// The resource path(s) to the message catalog. + /// Message catalogs are searched in the order given such + /// that the first one wins. By default, if no paths are + /// specified, we use connector-package.Messages.resx + /// + public string[] MessageCatalogPaths { get; set; } + + } + #endregion + + #region ConfigurationrClassAttribute + /// + /// The interface is traversed through reflection. This + /// annotation provides a way to override the default "add all property" behaviour. + /// + /// @since 1.4 + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ConfigurationClassAttribute : System.Attribute + { + + private readonly Boolean _skipUnsupported; + private readonly String[] _ignore; + + public ConfigurationClassAttribute(Boolean skipUnsupported, String[] ignore) + { + _skipUnsupported = skipUnsupported; + _ignore = ignore ?? new string[]{} ; + } + + /// + /// Silently skips properties with unsupported types. + /// + public Boolean SkipUnsupported + { + get + { + return _skipUnsupported; + } + } + + /// + /// List of properties which should be excluded from configuration properties.. + /// + public string[] Ignore + { + get + { + return _ignore; + } + } + } + #endregion + + #region ConfigurationPropertyAttribute + /// + /// The interface is traversed through reflection. This + /// annotation provides a way to override the default configuration operation for + /// each property. + /// + /// + /// + /// public class MyClass : Configuration { + /// [ConfigurationPropertionOptions(Confidential=true)] + /// public string MyProperty {get ; set;} + /// } + /// + /// + [AttributeUsage(AttributeTargets.Property)] + public class ConfigurationPropertyAttribute : System.Attribute + { + + /// + /// Order in which this property is displayed. + /// + public int Order { get; set; } + /// + /// Is this a confidential property whose value should be + /// encrypted by the application when persisted? + /// + public bool Confidential { get; set; } + /// + /// Is this a required property? + /// + public bool Required { get; set; } + /// + /// Change the default help message key. + /// + public string HelpMessageKey { get; set; } + /// + /// Change the default display message key. + /// + public string DisplayMessageKey { get; set; } + /// + /// Change the default group message key. + /// + public string GroupMessageKey { get; set; } + + /// + /// List of operations for which this property must be specified. + /// + /// + /// This is used for the case where a connector may or may not + /// implement certain operations depending in the configuration. + /// The default value of "empty array" is special in that + /// it means that this property is applicable to all operations. + /// MUST be SPI operations + /// + public Type[] OperationTypes { get; set; } + + /// + /// List of operations for which this property must be specified. + /// + /// + /// This is used for the case where a connector may or may not + /// implement certain operations depending in the configuration. + /// The default value of "empty array" is special in that + /// it means that this property is applicable to all operations. + /// + public SafeType[] Operations + { + get + { + Type[] types = OperationTypes; + SafeType[] rv = new SafeType[types.Length]; + for (int i = 0; i < types.Length; i++) + { + rv[i] = + SafeType.ForRawType(types[i]); + } + return rv; + } + } + + /// + /// Default constructor + /// + public ConfigurationPropertyAttribute() + { + Order = 1; + Confidential = false; + Required = false; + HelpMessageKey = null; + DisplayMessageKey = null; + OperationTypes = new Type[0]; + GroupMessageKey = null; + } + } + #endregion + + #region Connector + /// + /// This is the main interface to declare a connector. Developers must implement + /// this interface. The life-cycle for a is as follows + /// is called then any of the operations implemented + /// in the Connector and finally dispose. The and + /// allow for block operations. For instance bulk creates or + /// deletes and the use of before and after actions. Once is + /// called the object is discarded. + /// + public interface Connector : IDisposable + { + + /// + /// Initialize the connector with its configuration. For instance in a JDBC + /// Connector this would include the database URL, password, and user. + /// + /// instance of the object implemented by + /// the developer and populated with information + /// in order to initialize the . + void Init(Configuration configuration); + } + #endregion + + #region PoolableConnector + /// + /// To be implemented by Connectors that wish to be pooled. + /// + public interface PoolableConnector : Connector + { + /// + /// Checks if the connector is still alive. + /// + /// A connector can spend a large amount of time in the pool before + /// being used. This method is intended to check if the connector is + /// alive and operations can be invoked on it (for instance, an implementation + /// would check that the connector's physical connection to the resource + /// has not timed out). + /// + /// + /// The major difference between this method and is that + /// this method must do only the minimum that is necessary to check that the + /// connector is still alive. TestOp.Test() does a more thorough + /// check of the environment specified in the Configuration, and can therefore + /// be much slower. + /// + /// + /// This method can be called often. Implementations should do their + /// best to keep this method fast. + /// + /// + /// if the connector is no longer alive. + void CheckAlive(); + } + #endregion + + #region SearchResultsHandler + /// + /// A SearchResultsHandler is a completion handler for consuming the results of a + /// search request. + /// + /// A search result completion handler may be specified when performing search + /// requests using a + /// object. The method is invoked each time a matching + /// + /// resource is returned, followed by indicating that no + /// more ConnectorObject resources will be returned. + /// + /// + /// Implementations of these methods should complete in a timely manner so as to + /// avoid keeping the invoking thread from dispatching to other completion + /// handlers. + /// + /// + /// + /// Since 1.4 + public class SearchResultsHandler : ResultsHandler + { + + /// + /// Invoked when the request has completed successfully. + /// + /// + /// The query result indicating that no more resources are to be + /// returned and, if applicable, including information which + /// should be used for subsequent paged results query requests. + public Action HandleResult; + + } + #endregion + + #region StatefulConfiguration + /// + /// A Stateful Configuration interface extends the default + /// and makes the framework keep the same instance. + ///

+ /// The default Configuration object instance is constructed every single time + /// before the is called. If the + /// configuration class implements this interface then the Framework keeps one + /// instance of Configuration and the is + /// called with the same instance. This requires extra caution because the + /// framework only guaranties to create one instance and set the properties + /// before it calls the on different + /// connector instances in multiple different threads at the same time. The + /// Connector developer must quarantine that the necessary resource + /// initialisation are thread-safe. + /// + ///

+ /// If the connector implements the then this + /// configuration is kept in the + /// + /// and when the + /// + /// calls the method. If the connector implements only the + /// then this configuration is kept in the + /// and the + /// application must take care of releasing. + /// + ///

+ public interface StatefulConfiguration : Configuration + { + + /// + /// Release any allocated resources. + /// + void Release(); + + } + #endregion + + #region SyncTokenResultsHandler + /// + /// A SyncTokenResultsHandler is a Callback interface that an application + /// implements in order to handle results from + /// in a + /// stream-processing fashion. + /// + /// + /// Since 1.4 + public class SyncTokenResultsHandler : SyncResultsHandler + { + + /// + /// Invoked when the request has completed successfully. + /// + /// + /// The sync result indicating that no more resources are to be + /// returned and, if applicable, including information which + /// should be used for next sync requests. + //void HandleResult(SyncToken result); + public Action HandleResult; + + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/Framework/SpiOperations.cs b/dotnet/framework/Framework/SpiOperations.cs new file mode 100644 index 00000000..36516aa2 --- /dev/null +++ b/dotnet/framework/Framework/SpiOperations.cs @@ -0,0 +1,520 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using System.Collections.Generic; + +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; + +namespace Org.IdentityConnectors.Framework.Spi.Operations +{ + /// + /// Authenticate an object based on their unique identifier and password. + /// + public interface AuthenticateOp : SPIOperation + { + /// + /// Simple authentication with two parameters presumed to be user name and + /// password. + /// + /// + /// The developer is expected to attempt to + /// authenticate these credentials natively. If the authentication fails the + /// developer should throw a type of either + /// or if a native exception is available + /// and if its of type simple throw it. If the + /// native exception is not a wrap it in one and + /// throw it. This will provide the most detail for logging problem and + /// failed attempts. + /// + /// The developer is of course encourage to try and throw the most + /// informative exception as possible. In that regards there are several + /// exceptions provided in the exceptions package. For instance one of the + /// most common is . + /// + /// + /// + /// the name based credential for authentication. + /// the password based credential for authentication. + /// iff native authentication fails. If a native exception if + /// available attempt to throw it. + Uid Authenticate(ObjectClass objectClass, String username, GuardedString password, OperationOptions options); + } + + #region IConnectorEventSubscriptionOp + /// + /// A ConnectorEventSubscriptionOp. + /// + /// since 1.5 + public interface IConnectorEventSubscriptionOp : SPIOperation + { + ISubscription Subscribe(ObjectClass objectClass, Filter eventFilter, IObserver observer, OperationOptions operationOptions); + } + #endregion + + public interface ResolveUsernameOp : SPIOperation + { + /// + /// This is a companion to the simple with two parameters + /// presumed to be user name and password. + /// + /// + /// The difference is that this + /// method does not try to authenticate the credentials; instead, it + /// return the of the username that would be authenticated. + /// + /// If the authentication fails the + /// developer should throw a type of either + /// or if a native exception is available + /// and if its of type simple throw it. If the + /// native exception is not a wrap it in one and + /// throw it. This will provide the most detail for logging problem and + /// failed attempts. + /// + /// + /// The developer is of course encourage to try and throw the most + /// informative exception as possible. In that regards there are several + /// exceptions provided in the exceptions package. For instance one of the + /// most common is . + /// + /// + /// The object class to use for authenticate. + /// Will typically be an account. Will not be null. + /// the username that would be authenticated. Will not be null. + /// additional options that impact the way this operation is run. + /// If the caller passes null, the framework will convert this into + /// an empty set of options, so SPI need not worry + /// about this ever being null. + /// Uid The uid of the account that would be authenticated. + /// iff native authentication fails. If a native exception is + /// available attempt to throw it. + Uid ResolveUsername(ObjectClass objectClass, String username, OperationOptions options); + } + + /// + /// The developer is responsible for taking the attributes + /// given (which always includes the ) and create an object + /// and its . + /// + /// + /// The developer must return the + /// so that the caller can refer to the created object. + /// + /// The developer should make a best effort to create the + /// object otherwise throw an informative telling the + /// caller why the operation could not be completed. It reasonable to use + /// defaults for required s as long as they are documented. + /// + /// + /// Will Droste + /// $Revision $ + /// 1.0 + public interface CreateOp : SPIOperation + { + /// + /// The developer is responsible for taking the attributes + /// given (which always includes the ) and create an + /// object and its . + /// + /// + /// The developer must return + /// the so that the caller can refer to the created object. + /// + /// specifies the name of the object to create. + /// includes all the attributes necessary to create the resource + /// object including the attribute. + /// the unique id for the object that is created. For instance in + /// LDAP this would be the 'dn', for a database this would be the + /// primary key, and for 'ActiveDirectory' this would be the GUID. + Uid Create(ObjectClass oclass, ICollection attrs, OperationOptions options); + } + + /// + /// Deletes an object with the specified Uid and ObjectClass on the + /// resource. + /// + public interface DeleteOp : SPIOperation + { + /// + /// Delete the object that the specified Uid identifies (if any). + /// + /// The type of object to delete. + /// The unique identitfier for the object to delete. + /// Throws UnknowUid if the object does not exist. + void Delete(ObjectClass objClass, Uid uid, OperationOptions options); + } + + public interface SchemaOp : SPIOperation + { + /// + /// Determines what types of objects this supports. + /// + /// + /// This + /// method is considered an operation since determining supported objects may + /// require configuration information and allows this determination to be + /// dynamic. + /// + /// basic schema supported by this . + Schema Schema(); + } + /// + /// Operation that runs a script in the environment of the connector. + /// + /// + /// (Compare to , which runs a script + /// on the target resource that the connector manages.) + /// A connector that intends to provide to scripts + /// more than is required by the basic contract + /// specified in the javadoc for + /// should implement this interface. + /// + /// Each connector that implements this interface must support + /// at least the behavior specified by . + /// A connector also may expose additional variables for use by scripts + /// and may respond to specific . + /// Each connector that implements this interface + /// must describe in its javadoc as available "for use by connector scripts" + /// any such additional variables or supported options. + /// + /// + public interface ScriptOnConnectorOp : SPIOperation + { + + /// + /// Runs the script request. + /// + /// The script and arguments to run. + /// Additional options that control how the script is + /// run. + /// The result of the script. The return type must be + /// a type that the framework supports for serialization. + /// See for a list of supported types. + Object RunScriptOnConnector(ScriptContext request, + OperationOptions options); + } + /// + /// Operation that runs a script directly on a target resource. + /// + /// + /// (Compare to , which runs a script + /// in the context of a particular connector.) + /// + /// A connector that intends to support + /// + /// should implement this interface. Each connector that implements + /// this interface must document which script languages the connector supports, + /// as well as any supported . + /// + /// + public interface ScriptOnResourceOp : SPIOperation + { + /// + /// Run the specified script on the target resource + /// that this connector manages. + /// + /// The script and arguments to run. + /// Additional options that control + /// how the script is run. + /// The result of the script. The return type must be + /// a type that the framework supports for serialization. + /// See for a list of supported types. + Object RunScriptOnResource(ScriptContext request, + OperationOptions options); + } + #region SearchOp + /// + /// Implement this interface to allow the Connector to search for resource + /// objects. + /// + public interface SearchOp : SPIOperation where T : class + { + /// + /// Creates a filter translator that will translate + /// a specified filter to the native filter. + /// + /// + /// The + /// translated filters will be subsequently passed to + /// + /// + /// The object class for the search. Will never be null. + /// additional options that impact the way this operation is run. + /// If the caller passes null, the framework will convert this into + /// an empty set of options, so SPI need not worry + /// about this ever being null. + /// A filter translator. + FilterTranslator CreateFilterTranslator(ObjectClass oclass, OperationOptions options); + /// + /// This will be called by ConnectorFacade, once for each native query produced + /// by the FilterTranslator. + /// + /// + /// If there is more than one query the results will + /// automatically be merged together and duplicates eliminated. NOTE + /// that this implies an in-memory data structure that holds a set of + /// Uids, so memory usage in the event of multiple queries will be O(N) + /// where N is the number of results. That is why it is important that + /// the FilterTranslator implement OR if possible. + /// + /// The object class for the search. Will never be null. + /// The native query to run. A value of null means 'return everything for the given object class'. + /// Results should be returned to this handler + /// additional options that impact the way this operation is run. + /// If the caller passes null, the framework will convert this into + /// an empty set of options, so SPI need not worry + /// about this ever being null. + void ExecuteQuery(ObjectClass oclass, T query, ResultsHandler handler, OperationOptions options); + } + #endregion + + #region ISyncEventSubscriptionOp + /// + /// ASyncEventSubscriptionOp. + /// + /// since 1.5 + public interface ISyncEventSubscriptionOp : SPIOperation + { + ISubscription Subscribe(ObjectClass objectClass, SyncToken token, IObserver asyncHandler, OperationOptions operationOptions); + } + #endregion + + #region SyncOp + /// + /// Receive synchronization events from the resource. + /// + /// + public interface SyncOp : SPIOperation + { + /// + /// Perform a synchronization. + /// + /// The object class to synchronize. Must not be null. + /// The token representing the last token from the previous sync. + /// Should be null if this is the first sync for the given + /// resource. + /// The result handler Must not be null. + /// additional options that impact the way this operation is run. + /// If the caller passes null, the framework will convert this into + /// an empty set of options, so SPI need not worry + /// about this ever being null. + void Sync(ObjectClass objClass, SyncToken token, + SyncResultsHandler handler, + OperationOptions options); + /// + /// Returns the token corresponding to the latest sync delta. + /// + /// + /// This is to support applications that may wish to sync starting + /// "now". + /// + /// The latest token or null if there is no sync data. + SyncToken GetLatestSyncToken(ObjectClass objectClass); + } + #endregion + + /// + /// The developer of a Connector should implement either this interface or the + /// interface if the Connector will allow an authorized + /// caller to update (i.e., modify or replace) objects on the target resource. + /// + /// + /// + /// This update method is simpler to implement than {link UpdateAttributeValuesOp}, + /// which must handle any of several different types of update that the caller + /// may specify. However a true implementation of {link UpdateAttributeValuesOp} + /// offers better performance and atomicity semantics. + /// + /// + /// Will Droste + /// $Revision $ + /// 1.0 + public interface UpdateOp : SPIOperation + { + /// + /// Update the object specified by the and , + /// replacing the current values of each attribute with the values + /// provided. + /// + /// + /// + /// For each input attribute, replace + /// all of the current values of that attribute in the target object with + /// the values of that attribute. + /// + /// + /// If the target object does not currently contain an attribute that the + /// input set contains, then add this + /// attribute (along with the provided values) to the target object. + /// + /// + /// If the value of an attribute in the input set is + /// null, then do one of the following, depending on + /// which is most appropriate for the target: + /// + /// + /// If possible, remove that attribute from the target + /// object entirely. + /// + /// + /// + /// Otherwise, replace all of the current values of that + /// attribute in the target object with a single value of + /// null. + /// + /// + /// + /// + /// + /// the type of object to modify. Will never be null. + /// the uid of the object to modify. Will never be null. + /// set of new . the values in this set + /// represent the new, merged values to be applied to the object. + /// This set may also include . + /// Will never be null. + /// additional options that impact the way this operation is run. + /// Will never be null. + /// the of the updated object in case the update changes + /// the formation of the unique identifier. + /// iff the does not exist on the resource. + Uid Update(ObjectClass objclass, + Uid uid, + ICollection replaceAttributes, + OperationOptions options); + } + + /// + /// More advanced implementation of to be implemented by + /// connectors that wish to offer better performance and atomicity semantics + /// for the methods + /// and . + /// + public interface UpdateAttributeValuesOp : UpdateOp + { + + /// + /// Update the object specified by the and , + /// adding to the current values of each attribute the values provided. + /// + /// + /// + /// For each attribute that the input set contains, add to + /// the current values of that attribute in the target object all of the + /// values of that attribute in the input set. + /// + /// + /// NOTE that this does not specify how to handle duplicate values. + /// The general assumption for an attribute of a ConnectorObject + /// is that the values for an attribute may contain duplicates. + /// Therefore, in general simply append the provided values + /// to the current value for each attribute. + /// + /// + /// + /// + /// the type of object to modify. Will never be null. + /// the uid of the object to modify. Will never be null. + /// set of deltas. The values for the attributes + /// in this set represent the values to add to attributes in the object. + /// merged. This set will never include . + /// Will never be null. + /// additional options that impact the way this operation is run. + /// Will never be null. + /// the of the updated object in case the update changes + /// the formation of the unique identifier. + /// iff the does not exist on the resource. + Uid AddAttributeValues(ObjectClass objclass, + Uid uid, + ICollection valuesToAdd, + OperationOptions options); + + /// + /// Update the object specified by the and , + /// removing from the current values of each attribute the values provided. + /// + /// + /// + /// For each attribute that the input set contains, + /// remove from the current values of that attribute in the target object + /// any value that matches one of the values of the attribute from the input set. + /// + /// + /// NOTE that this does not specify how to handle unmatched values. + /// The general assumption for an attribute of a ConnectorObject + /// is that the values for an attribute are merely representational state. + /// Therefore, the implementer should simply ignore any provided value + /// that does not match a current value of that attribute in the target + /// object. Deleting an unmatched value should always succeed. + /// + /// + /// the type of object to modify. Will never be null. + /// the uid of the object to modify. Will never be null. + /// set of deltas. The values for the attributes + /// in this set represent the values to remove from attributes in the object. + /// merged. This set will never include . + /// Will never be null. + /// additional options that impact the way this operation is run. + /// Will never be null.. + /// the of the updated object in case the update changes + /// the formation of the unique identifier. + /// iff the does not exist on the resource. + Uid RemoveAttributeValues(ObjectClass objclass, + Uid uid, + ICollection valuesToRemove, + OperationOptions options); + } + + /// + /// Tests the connector . + /// + /// Unlike validation performed by , testing a configuration + /// checks that any pieces of environment referred by the configuration are available. + /// For example, the connector could make a physical connection to a host specified + /// in the configuration to check that it exists and that the credentials + /// specified in the configuration are usable. + /// + /// + /// This operation may be invoked before the configuration has been validated. + /// An implementation is free to validate the configuration before testing it. + /// + public interface TestOp : SPIOperation + { + /// + /// Tests the with the connector. + /// + /// iff the configuration is not valid or the test failed. Implementations + /// are encouraged to throw the most specific exception available. + /// When no specific exception is available, implementations can throw + /// . + void Test(); + } + + /// + /// Tagging interface for the SPI. + /// + public interface SPIOperation + { + } +} \ No newline at end of file diff --git a/dotnet/framework/Framework/version.template b/dotnet/framework/Framework/version.template new file mode 100644 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/Framework/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/Api.cs b/dotnet/framework/FrameworkInternal/Api.cs new file mode 100755 index 00000000..64bc8693 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/Api.cs @@ -0,0 +1,1550 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Impl.Api.Local; +using Org.IdentityConnectors.Framework.Impl.Api.Remote; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Pooling; +using Org.IdentityConnectors.Common.Proxy; +using Org.IdentityConnectors.Common.Security; +using System.Linq; +using System.Collections.Generic; +using System.Configuration; +using System.Globalization; +using System.Resources; +using System.Reflection; +using System.Diagnostics; +using System.Text; +using System.Threading; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using ConfigurationProperty = Org.IdentityConnectors.Framework.Api.ConfigurationProperty; + +namespace Org.IdentityConnectors.Framework.Impl.Api +{ + #region ConfigurationPropertyImpl + /// + /// Internal class, public only for unit tests + /// + public class ConfigurationPropertyImpl : ConfigurationProperty + { + private ICollection> _operations; + + public ConfigurationPropertyImpl() + { + } + + public ConfigurationPropertyImpl(ConfigurationPropertyImpl parent) + { + _operations = parent.Operations; + Parent = null; + Order = parent.Order; + Name = parent.Name; + HelpMessageKey = parent.HelpMessageKey; + DisplayMessageKey = parent.DisplayMessageKey; + Value = parent.Value; + GroupMessageKey = parent.GroupMessageKey; + ValueType = parent.ValueType; + IsConfidential = parent.IsConfidential; + IsRequired = parent.IsRequired; + } + + public ICollection> Operations + { + get + { + return _operations; + } + set + { + _operations = CollectionUtil.NewReadOnlySet(value); + } + } + + public ConfigurationPropertiesImpl Parent { get; set; } + + public int Order { get; set; } + + public string Name { get; set; } + + public string HelpMessageKey { get; set; } + + public string DisplayMessageKey { get; set; } + + public string GetHelpMessage(string def) + { + return FormatMessage(HelpMessageKey, def); + } + + public string GetDisplayName(string def) + { + return FormatMessage(DisplayMessageKey, def); + } + + public object Value { get; set; } + + public string GroupMessageKey { get; set; } + + public Type ValueType { get; set; } + + public bool IsConfidential { get; set; } + + public bool IsRequired { get; set; } + + public string GetGroup(string def) + { + return FormatMessage(GroupMessageKey, def); + } + + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + public override bool Equals(Object o) + { + ConfigurationPropertyImpl other = o as ConfigurationPropertyImpl; + if (other != null) + { + if (!Name.Equals(other.Name)) + { + return false; + } + if (!CollectionUtil.Equals(Value, other.Value)) + { + return false; + } + if (Order != other.Order) + { + return false; + } + if (!CollectionUtil.Equals(HelpMessageKey, other.HelpMessageKey)) + { + return false; + } + if (!CollectionUtil.Equals(DisplayMessageKey, other.DisplayMessageKey)) + { + return false; + } + if (!CollectionUtil.Equals(GroupMessageKey, other.GroupMessageKey)) + { + return false; + } + if (IsConfidential != other.IsConfidential) + { + return false; + } + if (IsRequired != other.IsRequired) + { + return false; + } + if (!CollectionUtil.Equals(ValueType, other.ValueType)) + { + return false; + } + if (!CollectionUtil.Equals(_operations, other._operations)) + { + return false; + } + + return true; + } + return false; + } + private String FormatMessage(String key, String dflt, params object[] args) + { + APIConfigurationImpl apiConfig = Parent.Parent; + ConnectorMessages messages = + apiConfig.ConnectorInfo.Messages; + return messages.Format(key, dflt, args); + } + } + #endregion + + #region ConfigurationPropertiesImpl + /// + /// Internal class, public only for unit tests + /// + public class ConfigurationPropertiesImpl : ConfigurationProperties + { + //properties, sorted by "order" + private IList _properties; + //property names, sorted by "order + private IList _propertyNames; + private IDictionary _propertiesByName; + + public IList Properties + { + get + { + return _properties; + } + set + { + ConfigurationPropertyImpl[] arr = + value == null ? new ConfigurationPropertyImpl[0] : value.ToArray(); + + Array.Sort(arr, + (p1, p2) => { return p1.Order.CompareTo(p2.Order); }); + _properties = + CollectionUtil.NewReadOnlyList(arr); + IList propertyNames = new List(); + IDictionary propertiesByName = + new Dictionary(); + foreach (ConfigurationPropertyImpl property in _properties) + { + propertyNames.Add(property.Name); + propertiesByName[property.Name] = property; + property.Parent = this; + } + _propertyNames = CollectionUtil.AsReadOnlyList(propertyNames); + _propertiesByName = CollectionUtil.AsReadOnlyDictionary(propertiesByName); + } + } + public ConfigurationProperty GetProperty(String name) + { + return CollectionUtil.GetValue(_propertiesByName, name, null); + } + public IList PropertyNames + { + get + { + return _propertyNames; + } + } + public APIConfigurationImpl Parent { get; set; } + public void SetPropertyValue(String name, object val) + { + ConfigurationProperty property = GetProperty(name); + if (property == null) + { + String MSG = "Property '" + name + "' does not exist."; + throw new ArgumentException(MSG); + } + property.Value = val; + } + + public override int GetHashCode() + { + return CollectionUtil.GetHashCode(_properties); + } + + public override bool Equals(object o) + { + ConfigurationPropertiesImpl other = o as ConfigurationPropertiesImpl; + if (other != null) + { + return CollectionUtil.SetsEqual(_properties, + other._properties); + } + return false; + } + } + #endregion + + #region APIConfigurationImpl + /// + /// Internal class, public only for unit tests + /// + public class APIConfigurationImpl : APIConfiguration + { + // ======================================================================= + // Fields + // ======================================================================= + /// + /// All configuration related to connector pooling. + /// + private ObjectPoolConfiguration _connectorPoolConfiguration; + + private ResultsHandlerConfiguration _resultsHandlerConfiguration; + private ConfigurationPropertiesImpl _configurationProperties; + private ICollection> _supportedOperations = + CollectionUtil.NewReadOnlySet>(new SafeType[0]); + + private IDictionary, int> _timeoutMap = + new Dictionary, int>(); + + public ConfigurationProperties ConfigurationProperties + { + get + { + return _configurationProperties; + } + set + { + if (_configurationProperties != null) + { + _configurationProperties.Parent = null; + } + _configurationProperties = (ConfigurationPropertiesImpl)value; + if (_configurationProperties != null) + { + _configurationProperties.Parent = this; + } + } + } + public IDictionary, int> TimeoutMap + { + get + { + return _timeoutMap; + } + set + { + _timeoutMap = value; + } + } + public bool IsConnectorPoolingSupported { get; set; } + public ObjectPoolConfiguration ConnectorPoolConfiguration + { + get + { + if (_connectorPoolConfiguration == null) + { + _connectorPoolConfiguration = new ObjectPoolConfiguration(); + } + return _connectorPoolConfiguration; + } + set + { + _connectorPoolConfiguration = value; + } + } + public ResultsHandlerConfiguration ResultsHandlerConfiguration + { + get + { + if (_resultsHandlerConfiguration == null) + { + _resultsHandlerConfiguration = new ResultsHandlerConfiguration(); + } + return _resultsHandlerConfiguration; + } + set + { + _resultsHandlerConfiguration = value; + } + } + public ICollection> SupportedOperations + { + get + { + return _supportedOperations; + } + set + { + _supportedOperations = CollectionUtil.NewReadOnlySet>(value); + } + } + + public bool IsSupportedOperation(SafeType api) + { + return _supportedOperations.Contains(api); + } + + public int GetTimeout(SafeType operation) + { + return Math.Max(CollectionUtil.GetValue(_timeoutMap, operation, + APIConstants.NO_TIMEOUT), APIConstants.NO_TIMEOUT); + } + public void SetTimeout(SafeType operation, int timeout) + { + _timeoutMap[operation] = timeout; + } + + public AbstractConnectorInfo ConnectorInfo { get; set; } + + public IConfigurationPropertyChangeListener ChangeListener { get; set; } + + public int ProducerBufferSize { get; set; } + + // ======================================================================= + // Constructors + // ======================================================================= + public APIConfigurationImpl() + { + ProducerBufferSize = 100; + } + + public APIConfigurationImpl(APIConfigurationImpl other) + { + if (null != other._connectorPoolConfiguration) + { + ConnectorPoolConfiguration = new ObjectPoolConfiguration(other._connectorPoolConfiguration); + } + if (null != other._resultsHandlerConfiguration) + { + ResultsHandlerConfiguration = new ResultsHandlerConfiguration(other._resultsHandlerConfiguration); + } + IsConnectorPoolingSupported = other.IsConnectorPoolingSupported; + ConfigurationPropertiesImpl prop = new ConfigurationPropertiesImpl(); + prop.Properties = ((ConfigurationPropertiesImpl)other.ConfigurationProperties).Properties; + ConfigurationProperties = prop; + + ProducerBufferSize = other.ProducerBufferSize; + TimeoutMap = new Dictionary, int>(other.TimeoutMap); + SupportedOperations = new HashSet>(other.SupportedOperations); + + ConnectorInfo = other.ConnectorInfo; + ChangeListener = other.ChangeListener; + } + } + #endregion + + #region AbstractConnectorInfo + /// + /// internal class, public only for unit tests + /// + public class AbstractConnectorInfo : ConnectorInfo + { + private APIConfigurationImpl _defaultAPIConfiguration; + + + public string GetConnectorDisplayName() + { + return Messages.Format(ConnectorDisplayNameKey, + ConnectorKey.ConnectorName); + } + + public ConnectorKey ConnectorKey { get; set; } + + public string ConnectorDisplayNameKey { get; set; } + + public string ConnectorCategoryKey { get; set; } + + public ConnectorMessages Messages { get; set; } + + public APIConfigurationImpl DefaultAPIConfiguration + { + get + { + return _defaultAPIConfiguration; + } + set + { + if (value != null) + { + value.ConnectorInfo = this; + } + _defaultAPIConfiguration = value; + } + } + + public APIConfiguration CreateDefaultAPIConfiguration() + { + APIConfigurationImpl rv = + (APIConfigurationImpl) + SerializerUtil.CloneObject(_defaultAPIConfiguration); + rv.ConnectorInfo = this; + return rv; + } + + public string GetConnectorCategory() + { + return Messages.Format(ConnectorCategoryKey, null); + } + } + #endregion + + #region ConnectorMessagesImpl + /// + /// internal class, public only for unit tests + /// + public class ConnectorMessagesImpl : ConnectorMessages + { + private IDictionary> + _catalogs = new Dictionary>(); + + public String Format(String key, String dflt, params object[] args) + { + if (key == null) + { + return dflt; + } + CultureInfo locale = CultureInfo.CurrentUICulture; + if (locale == null) + { + locale = CultureInfo.CurrentCulture; + } + if (dflt == null) + { + dflt = key; + } + CultureInfo foundCulture = locale; + String message = GetCatalogMessage(foundCulture, key); + //check neutral culture + if (message == null) + { + foundCulture = foundCulture.Parent; + message = GetCatalogMessage(foundCulture, key); + } + //check invariant culture + if (message == null) + { + foundCulture = foundCulture.Parent; + message = GetCatalogMessage(foundCulture, key); + } + //and default to framework + if (message == null) + { + message = GetFrameworkMessage(locale, key); + } + if (message == null) + { + return dflt; + } + else + { + //TODO: think more about this since the formatting + //is slightly different than Java + return String.Format(foundCulture, message, args); + } + } + + private String GetCatalogMessage(CultureInfo culture, String key) + { + IDictionary catalog = CollectionUtil.GetValue(_catalogs, culture, null); + return catalog != null ? CollectionUtil.GetValue(catalog, key, null) : null; + } + + private String GetFrameworkMessage(CultureInfo culture, String key) + { + ResourceManager manager = + new ResourceManager("Org.IdentityConnectors.Resources", + typeof(ConnectorMessagesImpl).Assembly); + String contents = (String)manager.GetObject(key, culture); + return contents; + } + + public IDictionary> Catalogs + { + get + { + return _catalogs; + } + set + { + if (value == null) + { + _catalogs = + new Dictionary>(); + } + else + { + _catalogs = value; + } + } + } + } + #endregion + + #region ConnectorInfoManagerFactoryImpl + public sealed class ConnectorInfoManagerFactoryImpl : + ConnectorInfoManagerFactory + { + private class RemoteManagerKey + { + private readonly String _host; + private readonly int _port; + + public RemoteManagerKey(RemoteFrameworkConnectionInfo info) + { + _host = info.Host; + _port = info.Port; + } + + public override bool Equals(Object o) + { + if (o is RemoteManagerKey) + { + RemoteManagerKey other = (RemoteManagerKey)o; + if (!_host.Equals(other._host)) + { + return false; + } + if (_port != other._port) + { + return false; + } + return true; + } + return false; + } + + public override int GetHashCode() + { + return _host.GetHashCode() ^ _port; + } + + } + + private Object LOCAL_LOCK = new Object(); + private Object REMOTE_LOCK = new Object(); + + private ConnectorInfoManager + _localManagerCache; + + private IDictionary + _remoteManagerCache = new Dictionary(); + + public ConnectorInfoManagerFactoryImpl() + { + + } + + public override void ClearRemoteCache() + { + lock (REMOTE_LOCK) + { + _remoteManagerCache.Clear(); + } + } + + public override ConnectorInfoManager GetLocalManager() + { + lock (LOCAL_LOCK) + { + ConnectorInfoManager rv = _localManagerCache; + if (rv == null) + { + rv = new LocalConnectorInfoManagerImpl(); + } + _localManagerCache = rv; + return rv; + } + } + + public override ConnectorInfoManager GetRemoteManager(RemoteFrameworkConnectionInfo info) + { + RemoteManagerKey key = new RemoteManagerKey(info); + lock (REMOTE_LOCK) + { + RemoteConnectorInfoManagerImpl rv = CollectionUtil.GetValue(_remoteManagerCache, key, null); + if (rv == null) + { + rv = new RemoteConnectorInfoManagerImpl(info); + } + _remoteManagerCache[key] = rv; + return rv.Derive(info); + } + } + + } + #endregion + + #region AbstractConnectorFacade + public abstract class AbstractConnectorFacade : ConnectorFacade + { + private readonly APIConfigurationImpl _configuration; + private readonly String _connectorFacadeKey; + + /// + /// Builds up the maps of supported operations and calls. + /// + public AbstractConnectorFacade(APIConfigurationImpl configuration) + { + Assertions.NullCheck(configuration, "configuration"); + //clone in case application tries to modify + //after the fact. this is necessary to + //ensure thread-safety of a ConnectorFacade + //also, configuration is used as a key in the + //pool, so it is important that it not be modified. + byte[] bytes = SerializerUtil.SerializeBinaryObject(configuration); + _connectorFacadeKey = Convert.ToBase64String(bytes); + _configuration = (APIConfigurationImpl)SerializerUtil.DeserializeBinaryObject(bytes); + //parent ref not included in the clone + _configuration.ConnectorInfo = configuration.ConnectorInfo; + _configuration.ChangeListener = configuration.ChangeListener; ; + } + + public AbstractConnectorFacade(string configuration, AbstractConnectorInfo connectorInfo) + : this(configuration, connectorInfo, null) + { + } + + /// + /// Builds up the maps of supported operations and calls. + /// + public AbstractConnectorFacade(string configuration, AbstractConnectorInfo connectorInfo, IConfigurationPropertyChangeListener changeListener) + { + Assertions.NullCheck(configuration, "configuration"); + Assertions.NullCheck(connectorInfo, "connectorInfo"); + _connectorFacadeKey = configuration; + _configuration = (APIConfigurationImpl)SerializerUtil.DeserializeBase64Object(configuration); + // parent ref not included in the clone + _configuration.ConnectorInfo = connectorInfo; + _configuration.ChangeListener = changeListener; + } + + /// + /// Return an instance of an API operation. + /// + /// + /// null if the operation is not support otherwise + /// return an instance of the operation. + /// + public APIOperation GetOperation(SafeType api) + { + if (!_configuration.IsSupportedOperation(api)) + { + return null; + } + return GetOperationImplementation(api); + } + + /// + /// Gets the unique generated identifier of this ConnectorFacade. + /// + /// It's not guarantied that the equivalent configuration will generate the + /// same configuration key. Always use the generated value and maintain it in + /// the external application. + /// + /// identifier of this ConnectorFacade instance. + public string ConnectorFacadeKey + { + get + { + return _connectorFacadeKey; + } + } + + public ICollection> SupportedOperations + { + get + { + return _configuration.SupportedOperations; + } + } + + // ======================================================================= + // Operation API Methods + // ======================================================================= + public Schema Schema() + { + return ((SchemaApiOp)GetOperationCheckSupported(SafeType.Get())) + .Schema(); + } + + public Uid Create(ObjectClass oclass, ICollection attrs, OperationOptions options) + { + return ((CreateApiOp)GetOperationCheckSupported(SafeType.Get())).Create(oclass, attrs, options); + } + + public void Delete(ObjectClass objClass, Uid uid, OperationOptions options) + { + ((DeleteApiOp)GetOperationCheckSupported(SafeType.Get())) + .Delete(objClass, uid, options); + } + + public SearchResult Search(ObjectClass objectClass, Filter filter, ResultsHandler handler, OperationOptions options) + { + return ((SearchApiOp)GetOperationCheckSupported(SafeType.Get())).Search( + objectClass, filter, handler, options); + } + + public Uid Update(ObjectClass objclass, Uid uid, ICollection attrs, OperationOptions options) + { + return ((UpdateApiOp)GetOperationCheckSupported(SafeType.Get())) + .Update(objclass, uid, attrs, options); + } + + public Uid AddAttributeValues( + ObjectClass objclass, + Uid uid, + ICollection attrs, + OperationOptions options) + { + return ((UpdateApiOp)GetOperationCheckSupported(SafeType.Get())) + .AddAttributeValues(objclass, uid, attrs, options); + } + + public Uid RemoveAttributeValues( + ObjectClass objclass, + Uid uid, + ICollection attrs, + OperationOptions options) + { + return ((UpdateApiOp)GetOperationCheckSupported(SafeType.Get())) + .RemoveAttributeValues(objclass, uid, attrs, options); + } + + public Uid Authenticate(ObjectClass objectClass, String username, GuardedString password, OperationOptions options) + { + return ((AuthenticationApiOp)GetOperationCheckSupported(SafeType.Get())).Authenticate( + objectClass, username, password, options); + } + + public Uid ResolveUsername(ObjectClass objectClass, String username, OperationOptions options) + { + return ((ResolveUsernameApiOp)GetOperationCheckSupported(SafeType.Get())).ResolveUsername( + objectClass, username, options); + } + + public ConnectorObject GetObject(ObjectClass objClass, Uid uid, OperationOptions options) + { + return ((GetApiOp)GetOperationCheckSupported(SafeType.Get())) + .GetObject(objClass, uid, options); + } + + public Object RunScriptOnConnector(ScriptContext request, + OperationOptions options) + { + return ((ScriptOnConnectorApiOp)GetOperationCheckSupported(SafeType.Get())) + .RunScriptOnConnector(request, options); + } + + public Object RunScriptOnResource(ScriptContext request, + OperationOptions options) + { + return ((ScriptOnResourceApiOp)GetOperationCheckSupported(SafeType.Get())) + .RunScriptOnResource(request, options); + } + + public void Test() + { + ((TestApiOp)GetOperationCheckSupported(SafeType.Get())).Test(); + } + + public void Validate() + { + ((ValidateApiOp)GetOperationCheckSupported(SafeType.Get())).Validate(); + } + + public SyncToken Sync(ObjectClass objectClass, SyncToken token, + SyncResultsHandler handler, + OperationOptions options) + { + return ((SyncApiOp)GetOperationCheckSupported(SafeType.Get())) + .Sync(objectClass, token, handler, options); + } + + public SyncToken GetLatestSyncToken(ObjectClass objectClass) + { + return ((SyncApiOp)GetOperationCheckSupported(SafeType.Get())) + .GetLatestSyncToken(objectClass); + } + + public ISubscription Subscribe(ObjectClass objectClass, Filter eventFilter, IObserver handler, + OperationOptions operationOptions) + { + return ((IConnectorEventSubscriptionApiOp)GetOperationCheckSupported(SafeType.Get())) + .Subscribe(objectClass, eventFilter, handler, operationOptions); + } + + public ISubscription Subscribe(ObjectClass objectClass, SyncToken token, IObserver handler, OperationOptions operationOptions) + { + return ((ISyncEventSubscriptionApiOp)GetOperationCheckSupported(SafeType.Get())) + .Subscribe(objectClass, token, handler, operationOptions); + } + + protected const String Msg = "Operation ''{0}'' not supported."; + + private APIOperation GetOperationCheckSupported(SafeType api) + { + // check if this operation is supported. + if (!SupportedOperations.Contains(api)) + { + + String str = String.Format(Msg, api); + throw new InvalidOperationException(str); + } + return GetOperationImplementation(api); + } + + /// + /// Gets the implementation of the given operation + /// + /// The operation to implement. + /// The implementation + protected abstract APIOperation GetOperationImplementation(SafeType api); + + protected APIConfigurationImpl GetAPIConfiguration() + { + return _configuration; + } + + /// + /// Creates a new proxy given a handler. + /// + protected APIOperation NewAPIOperationProxy(SafeType api, InvocationHandler handler) + { + return (APIOperation)Proxy.NewProxyInstance(api.RawType, handler); + } + + protected readonly static bool LOGGINGPROXY_ENABLED; + static AbstractConnectorFacade() + { + string enabled = ConfigurationManager.AppSettings.Get("logging.proxy"); + LOGGINGPROXY_ENABLED = StringUtil.IsTrue(enabled); + } + + /// + /// Creates the timeout proxy for the given operation. + /// + /// + /// The operation + /// + /// The underlying object + /// The proxy + protected internal APIOperation CreateTimeoutProxy(SafeType api, APIOperation target) + { + + int timeout = GetAPIConfiguration().GetTimeout(api); + int bufferSize = GetAPIConfiguration().ProducerBufferSize; + + DelegatingTimeoutProxy handler = new DelegatingTimeoutProxy(target, timeout, bufferSize); + + return NewAPIOperationProxy(api, handler); + } + + protected APIOperation CreateLoggingProxy(SafeType api, APIOperation target) + { + APIOperation ret = target; + if (LOGGINGPROXY_ENABLED) + { + LoggingProxy logging = new LoggingProxy(api, target); + ret = NewAPIOperationProxy(api, logging); + } + return ret; + } + } + #endregion + + #region ConnectorFacadeFactoryImpl + public class ConnectorFacadeFactoryImpl : ConnectorFacadeFactory + { + public override ConnectorFacade NewInstance(APIConfiguration config) + { + ConnectorFacade ret = null; + APIConfigurationImpl impl = (APIConfigurationImpl)config; + AbstractConnectorInfo connectorInfo = impl.ConnectorInfo; + if (connectorInfo is LocalConnectorInfoImpl) + { + LocalConnectorInfoImpl localInfo = + (LocalConnectorInfoImpl)connectorInfo; + // create a new Provisioner.. + ret = new LocalConnectorFacadeImpl(localInfo, impl); + } + else + { + ret = new RemoteConnectorFacadeImpl(impl); + } + return ret; + } + + public override ConnectorFacade NewInstance(ConnectorInfo connectorInfo, String config) + { + ConnectorFacade ret = null; + if (connectorInfo is LocalConnectorInfoImpl) + { + try + { + // create a new Provisioner. + ret = new LocalConnectorFacadeImpl((LocalConnectorInfoImpl)connectorInfo, config); + } + catch (Exception ex) + { + String connector = connectorInfo.ConnectorKey.ToString(); + Trace.TraceError("Failed to create new connector facade: {1}, {2}: {0}", connector, config, ex); + throw new ConnectorException(ex); + } + } + else if (connectorInfo is RemoteConnectorInfoImpl) + { + ret = new RemoteConnectorFacadeImpl((RemoteConnectorInfoImpl)connectorInfo, config); + } + return ret; + } + + /// + /// Dispose of all object pools and other resources associated with this + /// class. + /// + public override void Dispose() + { + ConnectorPoolManager.Dispose(); + } + + } + #endregion + + #region ManagedConnectorFacadeFactoryImpl + public class ManagedConnectorFacadeFactoryImpl : ConnectorFacadeFactoryImpl + { + + /// + /// Cache of the various ConnectorFacades. + /// + private static readonly ConcurrentDictionary> CACHE = + new ConcurrentDictionary>(); + + /// + /// {@inheritDoc} + /// + public override ConnectorFacade NewInstance(APIConfiguration config) + { + ConnectorFacade facade = base.NewInstance(config); + lock (CACHE) + { + Pair cachedFacade = CollectionUtil.GetValue(CACHE, facade.ConnectorFacadeKey, null); + if (null == cachedFacade) + { + CACHE[facade.ConnectorFacadeKey] = Pair.Of(DateTime.Now, facade); + } + else + { + Trace.TraceInformation("ConnectorFacade found in cache"); + cachedFacade.First = DateTime.Now; + facade = cachedFacade.Second; + } + } + return facade; + } + + public override ConnectorFacade NewInstance(ConnectorInfo connectorInfo, string config) + { + Pair facade = CollectionUtil.GetValue(CACHE, config, null); + if (null == facade) + { + lock (CACHE) + { + facade = CollectionUtil.GetValue(CACHE, config, null); + if (null == facade) + { + facade = Pair.Of(DateTime.Now, base.NewInstance(connectorInfo, config)); + CACHE[facade.Second.ConnectorFacadeKey] = facade; + } + else + { + facade.First = DateTime.Now; + Trace.TraceInformation("ConnectorFacade found in cache"); + } + } + } + else + { + facade.First = DateTime.Now; + } + return facade.Second; + } + + /// + /// Dispose of all object pools and other resources associated with this + /// class. + /// + public override void Dispose() + { + base.Dispose(); + foreach (Pair facade in CACHE.Values) + { + LocalConnectorFacadeImpl tmp = facade.Second as LocalConnectorFacadeImpl; + if (tmp != null) + { + try + { + tmp.Dispose(); + } + catch (Exception e) + { + Trace.TraceWarning("Failed to dispose facade: {0} {1}", e, facade); + } + } + } + CACHE.Clear(); + } + + public virtual void EvictIdle(TimeSpan unit) + { + if (unit == null) + { + throw new NullReferenceException(); + } + DateTime lastTime = DateTime.Now.Subtract(unit); + foreach (KeyValuePair> entry in CACHE) + { + if (lastTime.CompareTo(entry.Value.First) > 0) + { + Pair value; + if (CACHE.TryRemove(entry.Key, out value)) + { + if (value.Second is LocalConnectorFacadeImpl) + { + try + { + LocalConnectorFacadeImpl tmp = value.Second as LocalConnectorFacadeImpl; + if (tmp != null) + { + tmp.Dispose(); + Trace.TraceInformation("Disposed managed facade: {0}", entry.Value); + } + } + catch (Exception e) + { + Trace.TraceWarning("Failed to dispose facade: {0}, Exception: {1}", entry.Value, e); + } + } + } + } + } + } + + /// + /// Finds the {@code ConnectorFacade} in the cache. + /// + /// This is used for testing only. + /// + /// + /// the key to find the {@code ConnectorFacade}. + /// The {@code ConnectorFacade} or {@code null} if not found. + public virtual ConnectorFacade Find(string facadeKey) + { + Pair pair; + CACHE.TryGetValue(facadeKey, out pair); + if (pair != null) + { + return pair.Second; + } + return null; + } + } + #endregion + + #region ObjectStreamHandler + internal interface ObjectStreamHandler + { + bool Handle(Object obj); + } + #endregion + + #region StreamHandlerUtil + internal static class StreamHandlerUtil + { + /// + /// Adapts from a ObjectStreamHandler to a ResultsHandler + /// + private class ResultsHandlerAdapter + { + private readonly ObjectStreamHandler _target; + public ResultsHandlerAdapter(ObjectStreamHandler target) + { + _target = target; + } + + public ResultsHandler ResultsHandler + { + get + { + return new ResultsHandler() + { + Handle = obj => + { + return _target.Handle(obj); + } + + }; + } + } + } + + /// + /// Adapts from a ObjectStreamHandler to a SyncResultsHandler + /// + private class SyncResultsHandlerAdapter + { + private readonly ObjectStreamHandler _target; + public SyncResultsHandlerAdapter(ObjectStreamHandler target) + { + _target = target; + } + public SyncResultsHandler SyncResultsHandler + { + get + { + return new SyncResultsHandler() + { + + Handle = delta => + { + return _target.Handle(delta); + } + }; + } + } + } + + /// + /// Adapts from a ObjectStreamHandler to a SyncResultsHandler + /// + private class ObjectStreamHandlerAdapter : ObjectStreamHandler + { + private readonly Type _targetInterface; + private readonly Object _target; + public ObjectStreamHandlerAdapter(Type targetInterface, Object target) + { + Assertions.NullCheck(targetInterface, "targetInterface"); + Assertions.NullCheck(target, "target"); + if (!targetInterface.IsAssignableFrom(target.GetType())) + { + throw new ArgumentException("Target" + targetInterface + " " + target); + } + if (!IsAdaptableToObjectStreamHandler(targetInterface)) + { + throw new ArgumentException("Target interface not supported: " + targetInterface); + } + _targetInterface = targetInterface; + _target = target; + } + public bool Handle(Object obj) + { + if (_targetInterface.Equals(typeof(ResultsHandler))) + { + return ((ResultsHandler)_target).Handle((ConnectorObject)obj); + } + else if (_targetInterface.Equals(typeof(SyncResultsHandler))) + { + return ((SyncResultsHandler)_target).Handle((SyncDelta)obj); + } + else + { + throw new InvalidOperationException("Unhandled case: " + _targetInterface); + } + } + } + + public static bool IsAdaptableToObjectStreamHandler(Type clazz) + { + return (typeof(ResultsHandler).IsAssignableFrom(clazz) || + typeof(SyncResultsHandler).IsAssignableFrom(clazz)); + } + + public static ObjectStreamHandler AdaptToObjectStreamHandler(Type interfaceType, + Object target) + { + return new ObjectStreamHandlerAdapter(interfaceType, target); + } + public static Object AdaptFromObjectStreamHandler(Type interfaceType, + ObjectStreamHandler target) + { + if (interfaceType.Equals(typeof(ResultsHandler))) + { + return new ResultsHandlerAdapter(target).ResultsHandler; + } + else if (interfaceType.Equals(typeof(SyncResultsHandler))) + { + return new SyncResultsHandlerAdapter(target).SyncResultsHandler; + } + else + { + throw new InvalidOperationException("Unhandled case: " + interfaceType); + } + } + } + #endregion + + #region LoggingProxy + public class LoggingProxy : InvocationHandler + { + private readonly SafeType _op; + private readonly object _target; + private readonly Guid _guid; + + public LoggingProxy(SafeType api, object target) + { + _op = api; + _target = target; + _guid = Guid.NewGuid(); + } + /// + /// Log all operations. + /// + public Object Invoke(Object proxy, MethodInfo method, Object[] args) + { + //do not proxy equals, hashCode, toString + if (method.DeclaringType.Equals(typeof(object))) + { + return method.Invoke(this, args); + } + StringBuilder bld = new StringBuilder(); + bld.Append("Enter: "); + AddMethodName(bld, method); + bld.Append('('); + for (int i = 0; args != null && i < args.Length; i++) + { + if (i != 0) + { + bld.Append(", "); + } + bld.Append('{').Append(i).Append('}'); + } + bld.Append(')'); + // write out trace header + Trace.TraceInformation(bld.ToString(), args); + // invoke the method + try + { + object ret = method.Invoke(_target, args); + // clear out buffer. + bld.Length = 0; + bld.Append("Return: "); + AddMethodName(bld, method); + bld.Append("({0})"); + Trace.TraceInformation(bld.ToString(), ret); + return ret; + } + catch (TargetInvocationException e) + { + Exception root = e.InnerException; + ExceptionUtil.PreserveStackTrace(root); + LogException(method, root); + throw root; + } + catch (Exception e) + { + LogException(method, e); + throw e; + } + } + + private void AddMethodName(StringBuilder bld, MethodInfo method) + { + bld.Append(_op.RawType.Name); + bld.Append('.'); + bld.Append(method.Name); + bld.Append(" - id[").Append(_guid).Append(']'); + } + + private void LogException(MethodInfo method, Exception e) + { + StringBuilder bld = new StringBuilder(); + bld.Append("Exception: "); + AddMethodName(bld, method); + TraceUtil.ExceptionToString(bld, e, string.Empty); + + // write out trace header + Trace.TraceInformation(bld.ToString()); + } + } + #endregion + + #region DelegatingTimeoutProxy + /// + /// Delegating timeout proxy that selects the appropriate timeout handler + /// depending on the method. + /// + public class DelegatingTimeoutProxy : InvocationHandler + { + /// + /// Default timeout for all operations. + /// + public static int NO_TIMEOUT = -1; + + /// + /// The underlying operation that we are providing a timeout for + /// + private readonly object target; + + /// + /// The timeout + /// + private readonly int timeoutMillis; + + /// + /// The buffer size + /// + private readonly int bufferSize; + + /// + /// Create a new MethodTimeoutProxy. + /// + /// + /// The object we are wrapping + /// + public DelegatingTimeoutProxy(object target, int timeoutMillis, int bufferSize) + { + this.target = target; + this.timeoutMillis = timeoutMillis; + this.bufferSize = bufferSize; + } + + public Object Invoke(object proxy, MethodInfo method, object[] args) + { + + // do not timeout equals, hashCode, toString + if (method.DeclaringType.Equals(typeof(object))) + { + return method.Invoke(this, args); + } + + // figure out the actual handler that we want to delegate to + InvocationHandler handler = null; + + // if this is as stream handler method, we need the + // buffered results proxy (if configured) + if (IsStreamHandlerMethod(method)) + { + if (timeoutMillis != NO_TIMEOUT || bufferSize != 0) + { + // handler = new BufferedResultsProxy(target, bufferSize, timeoutMillis); + } + } + // otherwise it's a basic timeout proxy + else + { + if (timeoutMillis != NO_TIMEOUT) + { + // everything else is a general purpose timeout proxy + handler = new MethodTimeoutProxy(target, timeoutMillis); + } + } + + // delegate to the timeout handler if specified + if (handler != null) + { + return handler.Invoke(proxy, method, args); + } + // otherwise, pass the call directly to the object + else + { + try + { + return method.Invoke(target, args); + } + catch (TargetInvocationException e) + { + Exception root = e.InnerException; + ExceptionUtil.PreserveStackTrace(root); + throw root; + } + } + } + + private bool IsStreamHandlerMethod(MethodInfo method) + { + foreach (ParameterInfo paramType in method.GetParameters()) + { + if (StreamHandlerUtil.IsAdaptableToObjectStreamHandler(paramType.GetType())) + { + return true; + } + } + return false; + } + } + #endregion + + #region MethodTimeoutProxy + /// + /// General-purpose timeout proxy for providing timeouts on all methods on the + /// underlying object. Currently just used for APIOperations, but could wrap any + /// object. NOTE: this is not used for search because search needs timeout on an + /// element by element basis. Moreover, it would be unsafe for search since the + /// thread could continue to return elements after it has timed out and we need + /// to guarantee that not happen. + /// + public class MethodTimeoutProxy : InvocationHandler + { + /// + /// The underlying operation that we are providing a timeout for + /// + private readonly object target; + + /// + /// The timeout + /// + private readonly int timeoutMillis; + + /// + /// Create a new MethodTimeoutProxy. + /// + /// + /// The object we are wrapping + /// + public MethodTimeoutProxy(object target, int timeoutMillis) + { + this.target = target; + this.timeoutMillis = timeoutMillis; + } + + public Object Invoke(object proxy, MethodInfo method, Object[] args) + { + + // do not timeout equals, hashCode, toString + if (method.DeclaringType.Equals(typeof(object))) + { + return method.Invoke(this, args); + } + + //Locale locale = CurrentLocale.get(); + + try + { + var tokenSource = new CancellationTokenSource(); + CancellationToken token = tokenSource.Token; + //int timeOut = 1000; // 1000 ms + object result = null; + var task = Task.Factory.StartNew(() => + { + try + { + // propagate current locale + // since this is a thread pool + //CurrentLocale.set(locale); + result = method.Invoke(target, args); + } + catch (TargetInvocationException e) + { + Exception root = e.InnerException; + ExceptionUtil.PreserveStackTrace(root); + throw root; + } + finally + { + //CurrentLocale.clear(); + } + }, token); + if (!task.Wait(timeoutMillis, token)) + { + throw new OperationTimeoutException("The Task timed out!"); + } + return result; + } + catch (TimeoutException ex) + { + throw new OperationTimeoutException(ex); + } + catch (AggregateException ex) + { + throw ex.InnerException; + } + } + + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/ApiLocal.cs b/dotnet/framework/FrameworkInternal/ApiLocal.cs new file mode 100644 index 00000000..34b1038c --- /dev/null +++ b/dotnet/framework/FrameworkInternal/ApiLocal.cs @@ -0,0 +1,1616 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using System.Diagnostics; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Pooling; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Impl.Api.Remote; +using Org.IdentityConnectors.Framework.Impl.Api.Local.Operations; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Resources; +using System.Threading; +using System.IO; +using System.Linq; +using Org.IdentityConnectors.Common.Proxy; + +namespace Org.IdentityConnectors.Framework.Impl.Api.Local +{ + #region ConnectorPoolManager + public class ConnectorPoolManager + { + public class ConnectorPoolKey + { + private readonly ConnectorKey _connectorKey; + private readonly ConfigurationPropertiesImpl _configProperties; + private readonly ObjectPoolConfiguration _poolingConfig; + public ConnectorPoolKey(ConnectorKey connectorKey, + ConfigurationPropertiesImpl configProperties, + ObjectPoolConfiguration poolingConfig) + { + _connectorKey = connectorKey; + _configProperties = configProperties; + _poolingConfig = poolingConfig; + } + public override int GetHashCode() + { + return _connectorKey.GetHashCode(); + } + public override bool Equals(Object o) + { + if (o is ConnectorPoolKey) + { + ConnectorPoolKey other = (ConnectorPoolKey)o; + if (!_connectorKey.Equals(other._connectorKey)) + { + return false; + } + if (!_configProperties.Equals(other._configProperties)) + { + return false; + } + if (!_poolingConfig.Equals(other._poolingConfig)) + { + return false; + } + return true; + } + return false; + } + } + + private sealed class InternalConfigurationChangeHandler : AbstractConfiguration.IConfigurationChangeCallback + { + private readonly APIConfigurationImpl _apiConfiguration; + private readonly Configuration _configuration; + + public InternalConfigurationChangeHandler(APIConfigurationImpl apiConfiguration, Configuration configuration) + { + _apiConfiguration = apiConfiguration; + _configuration = configuration; + } + + public void NotifyUpdate() + { + try + { + IConfigurationPropertyChangeListener listener = _apiConfiguration.ChangeListener; + if (null != listener) + { + IList diff = + CSharpClassProperties.CalculateDiff(_apiConfiguration.ConfigurationProperties, _configuration); + if (diff.Count > 0) + { + listener.ConfigurationPropertyChange(diff); + } + } + } + catch (Exception e) + { + TraceUtil.TraceException("Configuration change notification is failed for" + _configuration.GetType(), e); + } + } + } + + private class ConnectorPoolHandler : ObjectPoolHandler + { + private readonly APIConfigurationImpl _apiConfiguration; + private readonly LocalConnectorInfoImpl _localInfo; + private readonly OperationalContext _context; + + public ConnectorPoolHandler(APIConfigurationImpl apiConfiguration, + LocalConnectorInfoImpl localInfo) + { + _apiConfiguration = apiConfiguration; + _localInfo = localInfo; + if (_localInfo.ConfigurationStateless) + { + _context = null; + } + else + { + _context = new OperationalContext(localInfo, apiConfiguration); + } + } + + public ObjectPoolConfiguration Validate(ObjectPoolConfiguration original) + { + ObjectPoolConfiguration configuration = (ObjectPoolConfiguration)SerializerUtil.CloneObject(original); + configuration.Validate(); + return configuration; + } + + public PoolableConnector MakeObject() + { + SafeType clazz = _localInfo.ConnectorClass; + PoolableConnector connector = null; + if (ReflectionUtil.IsParentTypeOf(typeof(PoolableConnector), clazz.RawType)) + { + + Configuration config = null; + if (null == _context) + { + config = + CSharpClassProperties.CreateBean( + (ConfigurationPropertiesImpl) _apiConfiguration.ConfigurationProperties, + _localInfo.ConnectorConfigurationClass); + if (null != _apiConfiguration.ChangeListener + && config is AbstractConfiguration) + { + ((AbstractConfiguration) config) + .AddChangeCallback(new InternalConfigurationChangeHandler( + _apiConfiguration, config)); + } + } + else + { + config = _context.GetConfiguration(); + } + + connector = (PoolableConnector)clazz.CreateInstance(); + connector.Init(config); + } + else + { + throw new ConnectorException("The Connector is not PoolableConnector: " + _localInfo.ConnectorKey); + } + return connector; + } + public void TestObject(PoolableConnector obj) + { + obj.CheckAlive(); + } + public void DisposeObject(PoolableConnector obj) + { + obj.Dispose(); + } + public void Shutdown() + { + if (null != _context) + { + _context.Dispose(); + } + } + } + + /// + /// Cache of the various _pools.. + /// + private static readonly IDictionary> + _pools = new Dictionary>(); + + + /// + /// Get a object pool for this connector if it supports connector pooling. + /// + public static Pair> GetPool(APIConfigurationImpl impl, LocalConnectorInfoImpl localInfo) + { + return GetPool2(impl, localInfo); + } + + public static ObjectPool GetPool(ConnectorPoolKey connectorPoolKey) + { + return CollectionUtil.GetValue(_pools, connectorPoolKey, null); + } + + /// + /// Get a object pool for this connector if it supports connector pooling. + /// + private static Pair> GetPool2(APIConfigurationImpl impl, LocalConnectorInfoImpl localInfo) + { + // determine if this connector wants generic connector pooling.. + if (impl.IsConnectorPoolingSupported) + { + ConnectorPoolKey key = new ConnectorPoolKey(impl.ConnectorInfo.ConnectorKey, + (ConfigurationPropertiesImpl)impl.ConfigurationProperties, impl.ConnectorPoolConfiguration); + lock (_pools) + { + // get the pool associated.. + ObjectPool pool = CollectionUtil.GetValue(_pools, key, null); + // create a new pool if it doesn't exist.. + if (pool == null) + { + Trace.TraceInformation("Creating new pool: {0}", impl.ConnectorInfo.ConnectorKey); + // this instance is strictly used for the pool.. + pool = new ObjectPool(new ConnectorPoolHandler(impl, localInfo), impl.ConnectorPoolConfiguration); + // add back to the map of POOLS.. + _pools[key] = pool; + } + + return Pair>.Of(key, pool); + } + } + else if (!localInfo.ConfigurationStateless) + { + return Pair>.Of(new ConnectorPoolKey(impl.ConnectorInfo.ConnectorKey, + (ConfigurationPropertiesImpl)impl.ConfigurationProperties, impl.ConnectorPoolConfiguration), (ObjectPool)null); + } + return Pair>.Of((ConnectorPoolKey)null, (ObjectPool)null); + } + + public static void Dispose(ConnectorPoolKey connectorPoolKey) + { + lock (_pools) + { + ObjectPool pool = null; + if (_pools.TryGetValue(connectorPoolKey, out pool)) + { + _pools.Remove(connectorPoolKey); + try + { + pool.Shutdown(); + } + catch (Exception e) + { + Trace.TraceWarning("Failed to close pool: {0} Exception: {1}", pool, e); + } + } + } + } + + public static void Dispose() + { + lock (_pools) + { + // close each pool.. + foreach (ObjectPool pool in _pools.Values) + { + try + { + pool.Shutdown(); + } + catch (Exception e) + { + Trace.TraceWarning("Failed to close pool: {0} {1}", e.Message, pool); + TraceUtil.TraceException("Failed to close pool", e); + } + } + // clear the map of all _pools.. + _pools.Clear(); + } + } + } + #endregion + + #region CSharpClassProperties + internal static class CSharpClassProperties + { + + /// + /// Calculate the difference between the given config and the properties. + /// + /// + /// + /// @return + /// + public static IList CalculateDiff(ConfigurationProperties properties, + Configuration config) + { + SafeType configType = SafeType.ForRawType(config.GetType()); + IDictionary descriptors = GetFilteredProperties(configType); + IList result = new List(); + var propertiesImpl = properties as ConfigurationPropertiesImpl; + if (null != propertiesImpl) + { + foreach (ConfigurationPropertyImpl property in propertiesImpl.Properties) + { + string name = property.Name; + PropertyInfo desc = descriptors[name]; + if (desc == null) + { + continue; + } + object oldValue = property.Value; + try + { + object newValue = desc.GetValue(config); + if (!CollectionUtil.Equals(oldValue, newValue)) + { + ConfigurationPropertyImpl change = new ConfigurationPropertyImpl(property) + { + Value = SerializerUtil.CloneObject(newValue) + }; + result.Add(change); + } + } + catch (Exception) + { + //Ignore + } + } + } + return result; + } + + public static ConfigurationPropertiesImpl + CreateConfigurationProperties(Configuration defaultObject) + { + SafeType config = SafeType.Get(defaultObject); + ConfigurationPropertiesImpl properties = + new ConfigurationPropertiesImpl(); + IList temp = + new List(); + IDictionary descs = GetFilteredProperties(config); + + foreach (PropertyInfo desc in descs.Values) + { + + String name = desc.Name; + + // get the configuration options.. + ConfigurationPropertyAttribute options = + GetPropertyOptions(desc); + // use the options to set internal properties.. + int order = 0; + String helpKey = "help_" + name; + String displKey = "display_" + name; + string grpKey = "group_" + name; + bool confidential = false; + bool required = false; + if (options != null) + { + // determine the display and help keys.. + if (!StringUtil.IsBlank(options.HelpMessageKey)) + { + helpKey = options.HelpMessageKey; + } + if (!StringUtil.IsBlank(options.DisplayMessageKey)) + { + displKey = options.DisplayMessageKey; + } + if (!StringUtil.IsBlank(options.GroupMessageKey)) + { + grpKey = options.GroupMessageKey; + } + // determine the order.. + order = options.Order; + required = options.Required; + confidential = options.Confidential; + } + Type type = desc.PropertyType; + if (!FrameworkUtil.IsSupportedConfigurationType(type)) + { + const String MSG = "Property type ''{0}'' is not supported."; + throw new ArgumentException(String.Format(MSG, type)); + } + + Object value = desc.GetValue(defaultObject, null); + + ConfigurationPropertyImpl prop = new ConfigurationPropertyImpl(); + prop.IsConfidential = confidential; + prop.IsRequired = required; + prop.DisplayMessageKey = displKey; + prop.HelpMessageKey = helpKey; + prop.GroupMessageKey = grpKey; + prop.Name = name; + prop.Order = order; + prop.Value = value; + prop.ValueType = type; + prop.Operations = options == null ? null : TranslateOperations(options.Operations); + + temp.Add(prop); + + } + properties.Properties = (temp); + return properties; + } + + private static ICollection> TranslateOperations(SafeType[] ops) + { + ICollection> set = + new HashSet>(); + foreach (SafeType spi in ops) + { + CollectionUtil.AddAll(set, FrameworkUtil.Spi2Apis(spi)); + } + return set; + } + + public static Configuration + CreateBean(ConfigurationPropertiesImpl properties, + SafeType configType) + { + Configuration rv = configType.CreateInstance(); + rv.ConnectorMessages = properties.Parent.ConnectorInfo.Messages; + MergeIntoBean(properties, rv); + return rv; + } + + public static void + MergeIntoBean(ConfigurationPropertiesImpl properties, + Configuration config) + { + SafeType configType = + SafeType.Get(config); + IDictionary descriptors = + GetFilteredProperties(configType); + foreach (ConfigurationPropertyImpl property in properties.Properties) + { + String name = property.Name; + PropertyInfo desc = + CollectionUtil.GetValue(descriptors, name, null); + if (desc == null) + { + String FMT = + "Class ''{0}'' does not have a property ''{1}''."; + String MSG = String.Format(FMT, + configType.RawType.Name, + name); + throw new ArgumentException(MSG); + } + object val = property.Value; + //some value types such as arrays + //are mutable. make sure the config object + //has its own copy + val = SerializerUtil.CloneObject(val); + desc.SetValue(config, val, null); + } + } + + private static IDictionary + GetFilteredProperties(SafeType config) + { + IDictionary rv = + new Dictionary(); + PropertyInfo[] descriptors = config.RawType.GetProperties(); + SortedSet excludes = new SortedSet(); + // exclude connectorMessages since its part of the interface. + excludes.Add("ConnectorMessages"); + + bool filterUnsupported = false; + ConfigurationClassAttribute options = GetConfigurationOptions(config); + if (null != options) + { + foreach (string s in options.Ignore) + { + excludes.Add(s); + } + filterUnsupported = options.SkipUnsupported; + } + foreach (PropertyInfo descriptor in descriptors) + { + String propName = descriptor.Name; + if (!descriptor.CanWrite) + { + //if there's no setter, ignore it + continue; + } + if (excludes.Contains(propName)) + { + continue; + } + if (filterUnsupported && !FrameworkUtil.IsSupportedConfigurationType(descriptor.PropertyType)) + { + //Silently ignore if the property type is not supported + continue; + } + if (!descriptor.CanRead) + { + const String FMT = + "Found setter ''{0}'' but not the corresponding getter."; + String MSG = String.Format(FMT, propName); + throw new ArgumentException(MSG); + } + rv[propName] = descriptor; + } + return rv; + } + + /// + /// Get the option from the property. + /// + private static ConfigurationPropertyAttribute GetPropertyOptions( + PropertyInfo propertyInfo) + { + Object[] objs = + propertyInfo.GetCustomAttributes( + typeof(ConfigurationPropertyAttribute), true); + if (objs.Length == 0) + { + return null; + } + else + { + return (ConfigurationPropertyAttribute)objs[0]; + } + } + + /// + /// Get the option from the property. + /// + private static ConfigurationClassAttribute GetConfigurationOptions( + SafeType configClass) + { + Object[] objs = + configClass.RawType.GetCustomAttributes( + typeof(ConfigurationClassAttribute), true); + if (objs.Length == 0) + { + return null; + } + else + { + return (ConfigurationClassAttribute)objs[0]; + } + } + + } + #endregion + + #region LocalConnectorInfoManagerImpl + internal class LocalConnectorInfoManagerImpl : ConnectorInfoManager + { + private IList _connectorInfo; + + public LocalConnectorInfoManagerImpl() + { + _connectorInfo = new List(); + Assembly assembly = Assembly.GetExecutingAssembly(); + FileInfo thisAssemblyFile = + new FileInfo(assembly.Location); + DirectoryInfo directory = + thisAssemblyFile.Directory; + FileInfo[] files = + directory.GetFiles("*.Connector.dll"); + foreach (FileInfo file in files) + { + Assembly lib = + Assembly.LoadFrom(file.ToString()); + CollectionUtil.AddAll(_connectorInfo, ConnectorAssemblyUtility.ProcessAssembly(lib)); + } + // also handle connector DLL file names with a version + FileInfo[] versionedFiles = directory.GetFiles("*.Connector-*.dll"); + foreach (FileInfo versionedFile in versionedFiles) + { + Assembly lib = + Assembly.LoadFrom(versionedFile.ToString()); + CollectionUtil.AddAll(_connectorInfo, ConnectorAssemblyUtility.ProcessAssembly(lib)); + } + } + + public ConnectorInfo FindConnectorInfo(ConnectorKey key) + { + foreach (ConnectorInfo info in _connectorInfo) + { + if (info.ConnectorKey.Equals(key)) + { + return info; + } + } + return null; + } + + public IList ConnectorInfos + { + get + { + return CollectionUtil.AsReadOnlyList(_connectorInfo); + } + } + } + + public static class ConnectorAssemblyUtility + { + public static IList ProcessAssembly(Assembly assembly) + { + IList rv = new List(); + + Type[] types = null; + try + { + types = assembly.GetTypes(); + } + catch (Exception e) + { + TraceUtil.TraceException("Unable to load assembly: " + assembly.FullName + ". Assembly will be ignored.", e); + if (e is System.Reflection.ReflectionTypeLoadException) + { + var typeLoadException = e as ReflectionTypeLoadException; + var loaderExceptions = typeLoadException.LoaderExceptions; + foreach (var item in loaderExceptions) + { + TraceUtil.TraceException(" - loader exception: " + item, item); + } + } + return rv; + } + + foreach (Type type in types) + { + Object[] attributes = type.GetCustomAttributes( + typeof(ConnectorClassAttribute), + false); + if (attributes.Length > 0) + { + ConnectorClassAttribute attribute = + (ConnectorClassAttribute)attributes[0]; + LocalConnectorInfoImpl info = CreateConnectorInfo(assembly, type, attribute); + UriBuilder uri = new UriBuilder(assembly.CodeBase); + Trace.TraceInformation("Add ConnectorInfo {0} to Local Connector Info Manager from {1}", + info.ConnectorKey, Uri.UnescapeDataString(uri.Path)); + + rv.Add(info); + } + } + return rv; + } + + public static LocalConnectorInfoImpl CreateConnectorInfo(Assembly assembly, + Type rawConnectorClass, + ConnectorClassAttribute attribute) + { + String fileName = assembly.Location; + if (!typeof(Connector).IsAssignableFrom(rawConnectorClass)) + { + String MSG = ("File " + fileName + + " declares a connector " + rawConnectorClass + + " that does not implement Connector."); + throw new ConfigurationException(MSG); + } + SafeType connectorClass = + SafeType.ForRawType(rawConnectorClass); + SafeType connectorConfigurationClass = attribute.ConnectorConfigurationType; + if (connectorConfigurationClass == null) + { + String MSG = ("File " + fileName + + " contains a ConnectorInfo attribute " + + "with no connector configuration class."); + throw new ConfigurationException(MSG); + } + String connectorDisplayNameKey = + attribute.ConnectorDisplayNameKey; + if (connectorDisplayNameKey == null) + { + String MSG = ("File " + fileName + + " contains a ConnectorInfo attribute " + + "with no connector display name."); + throw new ConfigurationException(MSG); + } + ConnectorKey key = + new ConnectorKey(assembly.GetName().Name, + assembly.GetName().Version.ToString(), + connectorClass.RawType.Namespace + "." + connectorClass.RawType.Name); + LocalConnectorInfoImpl rv = new LocalConnectorInfoImpl(); + rv.ConnectorClass = connectorClass; + rv.ConnectorConfigurationClass = connectorConfigurationClass; + rv.ConnectorDisplayNameKey = connectorDisplayNameKey; + rv.ConnectorCategoryKey = attribute.ConnectorCategoryKey; + rv.ConnectorKey = key; + rv.DefaultAPIConfiguration = CreateDefaultApiConfiguration(rv); + rv.Messages = LoadMessages(assembly, rv, attribute.MessageCatalogPaths); + return rv; + } + + public static APIConfigurationImpl + CreateDefaultApiConfiguration(LocalConnectorInfoImpl localInfo) + { + SafeType connectorClass = + localInfo.ConnectorClass; + APIConfigurationImpl rv = new APIConfigurationImpl(); + Configuration config = + localInfo.ConnectorConfigurationClass.CreateInstance(); + bool pooling = IsPoolingSupported(connectorClass); + rv.IsConnectorPoolingSupported = pooling; + rv.ConfigurationProperties = (CSharpClassProperties.CreateConfigurationProperties(config)); + rv.ConnectorInfo = (localInfo); + rv.SupportedOperations = (FrameworkUtil.GetDefaultSupportedOperations(connectorClass)); + return rv; + } + + private static bool IsPoolingSupported(SafeType clazz) + { + return ReflectionUtil.IsParentTypeOf(typeof(PoolableConnector), clazz.RawType); + } + + /// + /// Given an assembly, returns the list of cultures that + /// it is localized for + /// + /// + /// + private static CultureInfo[] GetLocalizedCultures(Assembly assembly) + { + FileInfo assemblyFile = + new FileInfo(assembly.Location); + DirectoryInfo directory = + assemblyFile.Directory; + IList temp = new List(); + DirectoryInfo[] subdirs = directory.GetDirectories(); + foreach (DirectoryInfo subdir in subdirs) + { + String name = subdir.Name; + CultureInfo cultureInfo; + //get the culture if the directory is the name of the + //culture + try + { + cultureInfo = new CultureInfo(name); + } + catch (ArgumentException) + { + //invalid culture + continue; + } + //see if there's a satellite assembly for this + try + { + assembly.GetSatelliteAssembly(cultureInfo); + } + catch (Exception) + { + //invalid assembly + continue; + } + temp.Add(cultureInfo); + } + temp.Add(CultureInfo.InvariantCulture); + return temp.ToArray(); + } + + public static ConnectorMessagesImpl LoadMessages(Assembly assembly, + LocalConnectorInfoImpl info, + String[] nameBases) + { + if (nameBases == null || nameBases.Length == 0) + { + String pkage = + info.ConnectorClass.RawType.Namespace; + nameBases = new String[] { pkage + ".Messages" }; + } + ConnectorMessagesImpl rv = new ConnectorMessagesImpl(); + CultureInfo[] cultures = GetLocalizedCultures(assembly); + for (int i = nameBases.Length - 1; i >= 0; i--) + { + String nameBase = nameBases[i]; + ResourceManager manager = new ResourceManager(nameBase, assembly); + foreach (CultureInfo culture in cultures) + { + ResourceSet resourceSet = manager.GetResourceSet(culture, true, false); + if (resourceSet != null) + { + IDictionary temp = + CollectionUtil.GetValue(rv.Catalogs, culture, null); + if (temp == null) + { + temp = new Dictionary(); + rv.Catalogs[culture] = temp; + } + foreach (System.Collections.DictionaryEntry entry in resourceSet) + { + String key = "" + entry.Key; + String val = "" + entry.Value; + temp[key] = val; + } + } + } + } + + return rv; + } + } + #endregion + + #region LocalConnectorInfoImpl + /// + /// Internal class, public only for unit tests + /// + public class LocalConnectorInfoImpl : AbstractConnectorInfo + { + public RemoteConnectorInfoImpl ToRemote() + { + RemoteConnectorInfoImpl rv = new RemoteConnectorInfoImpl(); + rv.ConnectorDisplayNameKey = ConnectorDisplayNameKey; + rv.ConnectorKey = ConnectorKey; + rv.DefaultAPIConfiguration = DefaultAPIConfiguration; + rv.Messages = Messages; + return rv; + } + public SafeType ConnectorClass { get; set; } + public SafeType ConnectorConfigurationClass { get; set; } + public bool ConfigurationStateless + { + get + { + return !ReflectionUtil.IsParentTypeOf(typeof(StatefulConfiguration), ConnectorConfigurationClass.RawType); + } + } + public bool ConnectorPoolingSupported + { + get + { + return ReflectionUtil.IsParentTypeOf(typeof(PoolableConnector), ConnectorClass.RawType); + } + } + } + #endregion + + #region LocalConnectorFacadeImpl + public class LocalConnectorFacadeImpl : AbstractConnectorFacade + { + // ======================================================================= + // Constants + // ======================================================================= + /// + /// Map the API interfaces to their implementation counterparts. + /// + private static readonly IDictionary, ConstructorInfo> API_TO_IMPL = + new Dictionary, ConstructorInfo>(); + + private static void AddImplementation(SafeType inter, + SafeType impl) + { + ConstructorInfo info = + impl.RawType.GetConstructor(new Type[]{typeof(ConnectorOperationalContext), + typeof(Connector)}); + if (info == null) + { + throw new ArgumentException(impl + " does not define the proper constructor"); + } + API_TO_IMPL[inter] = info; + } + + static LocalConnectorFacadeImpl() + { + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + AddImplementation(SafeType.Get(), + SafeType.Get()); + } + + // ======================================================================= + // Fields + // ======================================================================= + /// + /// Pool used to acquire connection from to use during operations. + /// + + /// + /// The connector info + /// + private readonly LocalConnectorInfoImpl connectorInfo; + + + /// + /// Shared OperationalContext for stateful facades + /// + private readonly ConnectorOperationalContext operationalContext; + + /// + /// Shared thread counter. + /// + private readonly ReferenceCounter referenceCounter = new ReferenceCounter(); + + /// + /// Builds up the maps of supported operations and calls. + /// + public LocalConnectorFacadeImpl(LocalConnectorInfoImpl connectorInfo, APIConfigurationImpl apiConfiguration) + : base(apiConfiguration) + { + this.connectorInfo = connectorInfo; + if (connectorInfo.ConfigurationStateless && !connectorInfo.ConnectorPoolingSupported) + { + operationalContext = null; + } + else + { + operationalContext = new ConnectorOperationalContext(connectorInfo, GetAPIConfiguration()); + } + } + + public LocalConnectorFacadeImpl(LocalConnectorInfoImpl connectorInfo, string configuration) + : base(configuration, connectorInfo) + { + this.connectorInfo = connectorInfo; + if (connectorInfo.ConfigurationStateless && !connectorInfo.ConnectorPoolingSupported) + { + operationalContext = null; + } + else + { + operationalContext = new ConnectorOperationalContext(connectorInfo, GetAPIConfiguration()); + } + } + + public LocalConnectorFacadeImpl( + LocalConnectorInfoImpl connectorInfo, String config, IConfigurationPropertyChangeListener changeListener) + : this(connectorInfo, config) + { + GetAPIConfiguration().ChangeListener = changeListener; + } + + public void Dispose() + { + if (null != operationalContext) + { + operationalContext.Dispose(); + } + } + + protected internal ConnectorOperationalContext OperationalContext + { + get + { + if (null == operationalContext) + { + return new ConnectorOperationalContext(connectorInfo, GetAPIConfiguration()); + } + return operationalContext; + } + } + + // ======================================================================= + // ConnectorFacade Interface + // ======================================================================= + + protected override APIOperation GetOperationImplementation(SafeType api) + { + APIOperation proxy; + Boolean enableTimeoutProxy = true; + //first create the inner proxy - this is the proxy that obtaining + //a connection from the pool, etc + //NOTE: we want to skip this part of the proxy for + //validate op, but we will want the timeout proxy + if (api.RawType.Equals(typeof(ValidateApiOp))) + { + OperationalContext context = + new OperationalContext(connectorInfo, GetAPIConfiguration()); + proxy = new ValidateImpl(context); + } + else if (api.RawType.Equals(typeof(GetApiOp))) + { + ConstructorInfo constructor = + API_TO_IMPL[SafeType.Get()]; + + ConnectorAPIOperationRunnerProxy handler = + new ConnectorAPIOperationRunnerProxy(OperationalContext, constructor); + proxy = + new GetImpl((SearchApiOp)NewAPIOperationProxy(SafeType.Get(), handler)); + } + else if (api.RawType == typeof(IConnectorEventSubscriptionApiOp)) + { + ConnectorAPIOperationRunnerProxy handler = new ConnectorAPIOperationRunnerProxy(OperationalContext, (opContext, connector) => new ConnectorEventSubscriptionApiOpImp( + opContext, connector, referenceCounter)); + proxy = NewAPIOperationProxy(api, handler); + enableTimeoutProxy = false; + } + else if (api.RawType == typeof(ISyncEventSubscriptionApiOp)) + { + ConnectorAPIOperationRunnerProxy handler = new ConnectorAPIOperationRunnerProxy(OperationalContext, (opContext, connector) => new SyncEventSubscriptionApiOpImpl( + opContext, connector, referenceCounter)); + proxy = NewAPIOperationProxy(api, handler); + enableTimeoutProxy = false; + } + else + { + ConstructorInfo constructor = + API_TO_IMPL[api]; + + ConnectorAPIOperationRunnerProxy handler = + new ConnectorAPIOperationRunnerProxy(OperationalContext, constructor); + proxy = + NewAPIOperationProxy(api, handler); + } + + if (enableTimeoutProxy) + { + // now wrap the proxy in the appropriate timeout proxy + proxy = CreateTimeoutProxy(api, proxy); + } + // add logging proxy.. + proxy = CreateLoggingProxy(api, proxy); + proxy = NewAPIOperationProxy(api, new ReferenceCountingProxy(proxy, referenceCounter)); + + return proxy; + } + public virtual bool IsUnusedFor(TimeSpan duration) + { + return referenceCounter.IsUnusedFor(duration); + } + + public class ReferenceCounter + { + private Int32 _threadCounts = 0; + private DateTime _lastUsed = DateTime.Now; + + public virtual bool IsUnusedFor(TimeSpan duration) + { + return _threadCounts == 0 && DateTime.Now - _lastUsed > duration; + } + + public virtual void Acquire() + { + Interlocked.Increment(ref _threadCounts); + } + + public virtual void Release() + { + if (Interlocked.Decrement(ref _threadCounts) <= 0) + { + _lastUsed = DateTime.Now; + + } + } + } + + private class ReferenceCountingProxy : InvocationHandler + { + private readonly object _target; + private readonly ReferenceCounter _referenceCounter; + + public ReferenceCountingProxy(object target, ReferenceCounter referenceCounter) + { + _target = target; + _referenceCounter = referenceCounter; + } + + public object Invoke(object proxy, MethodInfo method, object[] arguments) + { + // do not log equals, hashCode, toString + if (method.DeclaringType == typeof(object)) + { + return method.Invoke(this, arguments); + } + try + { + _referenceCounter.Acquire(); + return method.Invoke(_target, arguments); + } + catch (TargetInvocationException e) + { + Exception root = e.InnerException; + ExceptionUtil.PreserveStackTrace(root); + throw root; + } + finally + { + _referenceCounter.Release(); + } + } + } + } + #endregion + + #region ObjectPool + public class ObjectPool where T : class + { + /// + /// Statistics bean + /// + public sealed class Statistics + { + private readonly int _numIdle; + private readonly int _numActive; + + internal Statistics(int numIdle, int numActive) + { + _numIdle = numIdle; + _numActive = numActive; + } + + /// + /// Returns the number of idle objects + /// + public int NumIdle + { + get + { + return _numIdle; + } + } + + /// + /// Returns the number of active objects + /// + public int NumActive + { + get + { + return _numActive; + } + } + } + + /// + /// An object plus additional book-keeping + /// information about the object + /// + private class PooledObject where T2 : class + { + /// + /// The underlying object + /// + private readonly T2 _object; + + /// + /// True if this is currently active, false if + /// it is idle + /// + private bool _isActive; + + /// + /// Last state change (change from active to + /// idle or vice-versa) + /// + private long _lastStateChangeTimestamp; + + /// + /// Is this a freshly created object (never been pooled)? + /// + private bool _isNew; + + public PooledObject(T2 obj) + { + _object = obj; + _isNew = true; + Touch(); + } + + public T2 Object + { + get + { + return _object; + } + } + + public bool IsActive + { + get + { + return _isActive; + } + set + { + if (_isActive != value) + { + Touch(); + _isActive = value; + } + } + } + + public bool IsNew + { + get + { + return _isNew; + } + set + { + _isNew = value; + } + } + + + private void Touch() + { + _lastStateChangeTimestamp = DateTimeUtil.GetCurrentUtcTimeMillis(); + } + + public long LastStateChangeTimestamp + { + get + { + return _lastStateChangeTimestamp; + } + } + } + + /// + /// The lock object we use for everything + /// + private readonly Object LOCK = new Object(); + + /// + /// Map from the object to the + /// PooledObject (use IdentityHashMap so it's + /// always object equality) + /// + private readonly IDictionary> + _activeObjects = CollectionUtil.NewIdentityDictionary>(); + + /// + /// Queue of idle objects. + /// + /// + /// The one that has + /// been idle for the longest comes first in the queue + /// + private readonly LinkedList> + _idleObjects = new LinkedList>(); + + /// + /// ObjectPoolHandler we use for managing object lifecycle + /// + private readonly ObjectPoolHandler _handler; + + /// + /// Configuration for this pool. + /// + private readonly ObjectPoolConfiguration _config; + + /// + /// Is the pool shutdown + /// + private bool _isShutdown; + + /// + /// Create a new ObjectPool + /// + /// Handler for objects + /// Configuration for the pool + public ObjectPool(ObjectPoolHandler handler, + ObjectPoolConfiguration config) + { + + Assertions.NullCheck(handler, "handler"); + Assertions.NullCheck(config, "config"); + + _handler = handler; + //clone it + _config = + (ObjectPoolConfiguration)SerializerUtil.CloneObject(config); + //validate it + _config.Validate(); + + } + + /// + /// Return an object to the pool + /// + /// + public void ReturnObject(T obj) + { + Assertions.NullCheck(obj, "object"); + lock (LOCK) + { + //remove it from the active list + PooledObject pooled = + CollectionUtil.GetValue(_activeObjects, obj, null); + + //they are attempting to return something + //we haven't allocated (or that they've + //already returned) + if (pooled == null) + { + throw new InvalidOperationException("Attempt to return an object not in the pool: " + obj); + } + _activeObjects.Remove(obj); + + //set it to idle and add to idle list + //(this might get evicted right away + //by evictIdleObjects if we're over the + //limit or if we're shutdown) + pooled.IsActive = (false); + pooled.IsNew = (false); + _idleObjects.AddLast(pooled); + + //finally evict idle objects + EvictIdleObjects(); + + //wake anyone up who was waiting on a object + Monitor.PulseAll(LOCK); + } + } + + /// + /// Borrow an object from the pool. + /// + /// An object + public T BorrowObject() + { + while (true) + { + PooledObject rv = BorrowObjectNoTest(); + try + { + //make sure we are testing it outside + //of synchronization. otherwise this + //can create an IO bottleneck + _handler.TestObject(rv.Object); + return rv.Object; + } + catch (Exception) + { + //it's bad - remove from active objects + lock (LOCK) + { + _activeObjects.Remove(rv.Object); + } + DisposeNoException(rv.Object); + //if it's a new object, break out of the loop + //immediately + if (rv.IsNew) + { + throw; + } + } + } + } + + /// + /// Borrow an object from the pool, but don't test + /// it (it gets tested by the caller *outside* of + /// synchronization) + /// + /// the object + private PooledObject BorrowObjectNoTest() + { + //time when the call began + long startTime = DateTimeUtil.GetCurrentUtcTimeMillis(); + + lock (LOCK) + { + EvictIdleObjects(); + while (true) + { + if (_isShutdown) + { + throw new InvalidOperationException("Object pool already shutdown"); + } + + PooledObject pooledConn = null; + + //first try to recycle an idle object + if (_idleObjects.Count > 0) + { + pooledConn = _idleObjects.First(); + _idleObjects.RemoveFirst(); + } + //otherwise, allocate a new object if + //below the limit + else if (_activeObjects.Count < _config.MaxObjects) + { + pooledConn = + new PooledObject(_handler.MakeObject()); + } + + //if there's an object available, return it + //and break out of the loop + if (pooledConn != null) + { + pooledConn.IsActive = (true); + _activeObjects[pooledConn.Object] = + pooledConn; + return pooledConn; + } + + //see if we haven't timed-out yet + long elapsed = + DateTimeUtil.GetCurrentUtcTimeMillis() - startTime; + long remaining = _config.MaxWait - elapsed; + + //wait if we haven't timed out + if (remaining > 0) + { + Monitor.Wait(LOCK, (int)remaining); + } + else + { + //otherwise throw + throw new ConnectorException("Max objects exceeded"); + } + } + } + } + + /// + /// Closes any idle objects in the pool. + /// + /// + /// Existing active objects will remain alive and + /// be allowed to shutdown gracefully, but no more + /// objects will be allocated. + /// + public void Shutdown() + { + lock (LOCK) + { + _isShutdown = true; + //just evict idle objects + //if there are any active objects still + //going, leave them alone so they can return + //gracefully + try + { + EvictIdleObjects(); + } + finally + { + _handler.Shutdown(); + } + //wake anyone up who was waiting on an object + Monitor.PulseAll(LOCK); + } + } + + /// + /// Gets a snapshot of the pool's stats at a point in time. + /// + /// The statistics + public Statistics GetStatistics() + { + lock (LOCK) + { + return new Statistics(_idleObjects.Count, + _activeObjects.Count); + } + } + + /// + /// Evicts idle objects as needed (evicts + /// all idle objects if we're shutdown) + /// + private void EvictIdleObjects() + { + while (TooManyIdleObjects()) + { + PooledObject conn = _idleObjects.First(); + _idleObjects.RemoveFirst(); + DisposeNoException(conn.Object); + } + } + + /// + /// Returns true if any of the following are true: + /// + /// + /// We're shutdown and there are idle objects + /// + /// + /// + /// Max idle objects exceeded + /// + /// + /// + /// Min idle objects exceeded and there are old objects + /// + /// + /// + /// + private bool TooManyIdleObjects() + { + + if (_isShutdown && _idleObjects.Count > 0) + { + return true; + } + + if (_config.MaxIdle < _idleObjects.Count) + { + return true; + } + if (_config.MinIdle >= _idleObjects.Count) + { + return false; + } + + PooledObject oldest = + _idleObjects.First(); + + long age = + (DateTimeUtil.GetCurrentUtcTimeMillis() - oldest.LastStateChangeTimestamp); + + + return age > _config.MinEvictableIdleTimeMillis; + } + + /// + /// Dispose of an object, but don't throw any exceptions + /// + /// + private void DisposeNoException(T obj) + { + try + { + _handler.DisposeObject(obj); + } + catch (Exception e) + { + TraceUtil.TraceException("disposeObject() is not supposed to throw", e); + } + } + } + #endregion + + #region ObjectPoolHandler + public interface ObjectPoolHandler where T : class + { + /// + /// Validates, copies and updates the original + /// {@code ObjectPoolConfiguration}. + ///

+ /// This class can validate and if necessary it changes the {@code original} + /// configuration. + ///

+ /// + /// custom configured instance. + /// new instance of the {@code original} config. + ObjectPoolConfiguration Validate(ObjectPoolConfiguration original); + + /// + /// Makes a new instance of the pooled object. + ///

+ /// This method is called whenever a new instance is needed. + ///

+ /// new instance of T. + T MakeObject(); // T NewObject(); + + /// + /// Tests the borrowed object. + ///

+ /// This method is invoked on head instances to make sure they can be + /// borrowed from the pool. + ///

+ /// + /// the pooled object. + void TestObject(T obj); + + /// + /// Disposes the object. + ///

+ /// This method is invoked on every instance when it is being "dropped" from + /// the pool (whether due to the response from , + /// or for reasons specific to the pool implementation.) + ///

+ /// + /// The "dropped" object. + void DisposeObject(T obj); + + /// + /// Releases any allocated resources. + ///

+ /// Existing active objects will remain alive and be allowed to shutdown + /// gracefully, but no more objects will be allocated. + ///

+ void Shutdown(); + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/ApiLocalOperations.cs b/dotnet/framework/FrameworkInternal/ApiLocalOperations.cs new file mode 100644 index 00000000..cd8c217b --- /dev/null +++ b/dotnet/framework/FrameworkInternal/ApiLocalOperations.cs @@ -0,0 +1,2193 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014-2015 ForgeRock AS. + */ +using System; + +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Proxy; +using Org.IdentityConnectors.Common.Script; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; +using System.Reflection; +using System.Collections.Generic; +using System.Linq; +using Org.IdentityConnectors.Framework.Api; +using System.Text; +using System.Diagnostics; +using System.Threading; + +namespace Org.IdentityConnectors.Framework.Impl.Api.Local.Operations +{ + #region APIOperationRunner + /// + /// NOTE: internal class, public only for unit tests + /// Base class for API operation runners. + /// + public abstract class APIOperationRunner + { + /// + /// Context that has all the information required to execute an operation. + /// + private readonly OperationalContext _context; + + /// + /// Creates the API operation so it can called multiple times. + /// + public APIOperationRunner(OperationalContext context) + { + _context = context; + //TODO: verify this + // get the APIOperation that this class implements.. + //List> apiOps = getInterfaces(this + //.getClass(), APIOperation.class); + // there should be only one.. + //if (apiOps.size() > 1) { + // final String MSG = "Must only implement one operation."; + // throw new IllegalStateException(MSG); + //} + } + + /// + /// Get the current operational context. + /// + public OperationalContext GetOperationalContext() + { + return _context; + } + + } + #endregion + + #region ConnectorAPIOperationRunner + /// + /// NOTE: internal class, public only for unit tests + /// Subclass of APIOperationRunner for operations that require a connector. + /// + public abstract class ConnectorAPIOperationRunner : APIOperationRunner + { + /// + /// The connector instance + /// + private readonly Connector _connector; + + /// + /// Creates the API operation so it can called multiple times. + /// + public ConnectorAPIOperationRunner(ConnectorOperationalContext context, + Connector connector) + : base(context) + { + _connector = connector; + } + + public Connector GetConnector() + { + return _connector; + } + + public ObjectNormalizerFacade GetNormalizer(ObjectClass oclass) + { + AttributeNormalizer norm = null; + Connector connector = GetConnector(); + if (connector is AttributeNormalizer) + { + norm = (AttributeNormalizer)connector; + } + return new ObjectNormalizerFacade(oclass, norm); + } + } + #endregion + + #region ConnectorAPIOperationRunnerProxy + /// + /// Proxy for APIOperationRunner that takes care of setting up underlying + /// connector and creating the implementation of APIOperationRunner. + /// + /// + /// The implementation of APIOperationRunner gets created whenever the + /// actual method is invoked. + /// + internal class ConnectorAPIOperationRunnerProxy : InvocationHandler + { + /// + /// The operational context + /// + private readonly ConnectorOperationalContext _context; + + /// + /// The implementation constructor. + /// + /// + /// The instance is lazily created upon + /// invocation + /// + private readonly ConstructorInfo _runnerImplConstructor; + + private readonly Func _runnerImplFunc; + + /// + /// Create an APIOperationRunnerProxy + /// + /// The operational context + /// The implementation constructor. Implementation + /// must define a two-argument constructor(OperationalContext,Connector) + public ConnectorAPIOperationRunnerProxy(ConnectorOperationalContext context, + ConstructorInfo runnerImplConstructor) + { + _context = context; + _runnerImplConstructor = runnerImplConstructor; + } + + public ConnectorAPIOperationRunnerProxy(ConnectorOperationalContext context, + Func runnerImplConstructor) + { + _context = context; + _runnerImplFunc = runnerImplConstructor; + } + + public Object Invoke(Object proxy, MethodInfo method, object[] args) + { + //do not proxy equals, hashCode, toString + if (method.DeclaringType.Equals(typeof(object))) + { + return method.Invoke(this, args); + } + object ret = null; + Connector connector = null; + ObjectPool pool = _context.Pool; + // get the connector class.. + SafeType connectorClazz = _context.GetConnectorClass(); + try + { + // pooling is implemented get one.. + if (pool != null) + { + connector = pool.BorrowObject(); + } + else + { + // get a new instance of the connector.. + connector = connectorClazz.CreateInstance(); + // initialize the connector.. + connector.Init(_context.GetConfiguration()); + } + APIOperationRunner runner = null != _runnerImplFunc ? _runnerImplFunc(_context, connector) : + (APIOperationRunner)_runnerImplConstructor.Invoke(new object[]{ + _context, + connector}); + ret = method.Invoke(runner, args); + // call out to the operation.. + if (ret is ISubscription) + { + //Dispose later + ret = new DeferredSubscriptionDisposer((ISubscription)ret, connector, pool); + connector = null; + } + } + catch (TargetInvocationException e) + { + Exception root = e.InnerException; + ExceptionUtil.PreserveStackTrace(root); + throw root; + } + finally + { + // make sure dispose of the connector properly + if (connector != null) + { + DisposeConnector(connector, pool); + } + } + return ret; + } + + internal static void DisposeConnector(Connector connector, ObjectPool pool) + { + // determine if there was a pool.. + if (pool != null) + { + try + { + //try to return it to the pool even though an + //exception may have happened that leaves it in + //a bad state. The contract of checkAlive + //is that it will tell you if the connector is + //still valid and so we leave it up to the pool + //and connector to work it out. + pool.ReturnObject((PoolableConnector)connector); + } + catch (Exception e) + { + //don't let pool exceptions propagate or mask + //other exceptions. do log it though. + TraceUtil.TraceException(null, e); + } + } + //not pooled - just dispose + else + { + //dispose it not supposed to throw, but just in case, + //catch the exception and log it so we know about it + //but don't let the exception prevent additional + //cleanup that needs to happen + try + { + connector.Dispose(); + } + catch (Exception e) + { + //log this though + TraceUtil.TraceException(null, e); + } + } + } + private sealed class DeferredSubscriptionDisposer : ISubscription + { + private readonly Connector _connector; + private readonly ObjectPool _poolEntry; + private readonly ISubscription _subscription; + private Int32 _active = 1; + public DeferredSubscriptionDisposer(ISubscription subscription, Connector connector, ObjectPool poolEntry) + { + _subscription = subscription; + _connector = connector; + _poolEntry = poolEntry; + } + + public void Dispose() + { + try + { + _subscription.Dispose(); + } + finally + { + if (Interlocked.CompareExchange(ref _active, 0, 1) == 1) + { + DisposeConnector(_connector, _poolEntry); + } + } + } + + public bool Unsubscribed + { + get + { + return _subscription.Unsubscribed; + } + } + } + } + + #endregion + + #region ConnectorOperationalContext + /// + /// NOTE: internal class, public only for unit tests + /// Simple structure to pass more variables through the constructor of + /// . + /// + public class ConnectorOperationalContext : OperationalContext + { + /// + /// Pool Key for Connectors + /// + private ConnectorPoolManager.ConnectorPoolKey connectorPoolKey; + + public ConnectorOperationalContext(LocalConnectorInfoImpl connectorInfo, APIConfigurationImpl apiConfiguration) + : base(connectorInfo, apiConfiguration) + { + } + + public ObjectPool Pool + { + get + { + if (apiConfiguration.IsConnectorPoolingSupported) + { + if (null == connectorPoolKey) + { + Pair> pool = ConnectorPoolManager.GetPool(apiConfiguration, connectorInfo); + + connectorPoolKey = pool.First; + return pool.Second; + } + else + { + ObjectPool pool = ConnectorPoolManager.GetPool(connectorPoolKey); + if (null == pool) + { + // + Pair> poolPair = ConnectorPoolManager.GetPool(apiConfiguration, connectorInfo); + + connectorPoolKey = poolPair.First; + pool = poolPair.Second; + } + return pool; + } + } + else + { + return null; + } + } + } + + public SafeType GetConnectorClass() + { + return GetConnectorInfo().ConnectorClass; + } + + public override void Dispose() + { + base.Dispose(); + if (null != connectorPoolKey) + { + ConnectorPoolManager.Dispose(connectorPoolKey); + connectorPoolKey = null; + } + } + + } + #endregion + + #region AuthenticationImpl + internal class AuthenticationImpl : ConnectorAPIOperationRunner, + AuthenticationApiOp + { + /// + /// Pass the configuration etc to the abstract class. + /// + public AuthenticationImpl(ConnectorOperationalContext context, + Connector connector) + : base(context, connector) + { + } + + /// + /// Authenticate using the basic credentials. + /// + /// + public Uid Authenticate(ObjectClass objectClass, String username, GuardedString password, OperationOptions options) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new System.NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(username, "username"); + Assertions.NullCheck(password, "password"); + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + return ((AuthenticateOp)GetConnector()).Authenticate(objectClass, username, password, options); + } + } + #endregion + + #region ResolveUsernameImpl + internal class ResolveUsernameImpl : ConnectorAPIOperationRunner, + ResolveUsernameApiOp + { + /// + /// Pass the configuration etc to the abstract class. + /// + public ResolveUsernameImpl(ConnectorOperationalContext context, + Connector connector) + : base(context, connector) + { + } + + /// + /// Resolve the username to an Uid. + /// + public Uid ResolveUsername(ObjectClass objectClass, String username, OperationOptions options) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new System.NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(username, "username"); + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + return ((ResolveUsernameOp)GetConnector()).ResolveUsername(objectClass, username, options); + } + } + #endregion + + #region CreateImpl + internal class CreateImpl : ConnectorAPIOperationRunner, + CreateApiOp + { + /// + /// Initializes the operation works. + /// + public CreateImpl(ConnectorOperationalContext context, + Connector connector) + : base(context, connector) + { + } + + /// + /// Calls the create method on the Connector side. + /// + /// + public Uid Create(ObjectClass objectClass, ICollection createAttributes, OperationOptions options) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new System.NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(createAttributes, "createAttributes"); + // check to make sure there's not a uid.. + if (ConnectorAttributeUtil.GetUidAttribute(createAttributes) != null) + { + throw new InvalidAttributeValueException("Parameter 'createAttributes' contains a uid."); + } + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + // validate input.. + HashSet dups = new HashSet(); + foreach (ConnectorAttribute attr in createAttributes) + { + if (dups.Contains(attr.Name)) + { + throw new InvalidAttributeValueException("Duplicate attribute name exists: " + attr.Name); + } + dups.Add(attr.Name); + } + Connector connector = GetConnector(); + ObjectNormalizerFacade normalizer = GetNormalizer(objectClass); + ICollection normalizedAttributes = + normalizer.NormalizeAttributes(createAttributes); + // create the object.. + Uid ret = ((CreateOp)connector).Create(objectClass, normalizedAttributes, options); + return (Uid)normalizer.NormalizeAttribute(ret); + } + } + #endregion + + #region DeleteImpl + internal class DeleteImpl : ConnectorAPIOperationRunner, + DeleteApiOp + { + /// + /// Initializes the operation works. + /// + public DeleteImpl(ConnectorOperationalContext context, + Connector connector) + : base(context, connector) + { + } + /// + /// Calls the delete method on the Connector side. + /// + /// + public void Delete(ObjectClass objectClass, Uid uid, OperationOptions options) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new System.NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(uid, "uid"); + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + Connector connector = GetConnector(); + ObjectNormalizerFacade normalizer = GetNormalizer(objectClass); + // delete the object.. + ((DeleteOp)connector).Delete(objectClass, + (Uid)normalizer.NormalizeAttribute(uid), + options); + } + } + #endregion + + #region AttributesToGetResultsHandler + public abstract class AttributesToGetResultsHandler + { + // ======================================================================= + // Fields + // ======================================================================= + private readonly string[] _attrsToGet; + + // ======================================================================= + // Constructors + // ======================================================================= + /// + /// Keep the attribute to get.. + /// + public AttributesToGetResultsHandler(string[] attrsToGet) + { + Assertions.NullCheck(attrsToGet, "attrsToGet"); + _attrsToGet = attrsToGet; + } + + /// + /// Simple method that clones the object and remove the attribute thats are + /// not in the set. + /// + /// case insensitive set of attribute names. + public ICollection ReduceToAttrsToGet( + ICollection attrs) + { + ICollection ret = new HashSet(); + IDictionary map = ConnectorAttributeUtil.ToMap(attrs); + foreach (string attrName in _attrsToGet) + { + ConnectorAttribute attr = CollectionUtil.GetValue(map, attrName, null); + // TODO: Should we throw if the attribute is not yet it was + // requested?? Or do we ignore because the API maybe asking + // for what the resource doesn't have?? + if (attr != null) + { + ret.Add(attr); + } + } + return ret; + } + public ConnectorObject ReduceToAttrsToGet(ConnectorObject obj) + { + // clone the object and reduce the attributes only the set of + // attributes. + ConnectorObjectBuilder bld = new ConnectorObjectBuilder(); + bld.SetUid(obj.Uid); + bld.SetName(obj.Name); + bld.ObjectClass = obj.ObjectClass; + ICollection objAttrs = obj.GetAttributes(); + ICollection attrs = ReduceToAttrsToGet(objAttrs); + bld.AddAttributes(attrs); + return bld.Build(); + } + } + #endregion + + #region SearchAttributesToGetResultsHandler + public sealed class SearchAttributesToGetResultsHandler : + AttributesToGetResultsHandler + { + // ======================================================================= + // Fields + // ======================================================================= + private readonly ResultsHandler _handler; + + // ======================================================================= + // Constructors + // ======================================================================= + public SearchAttributesToGetResultsHandler( + ResultsHandler handler, string[] attrsToGet) + : base(attrsToGet) + { + Assertions.NullCheck(handler, "handler"); + this._handler = handler; + } + + public ResultsHandler ResultsHandler + { + get + { + return new ResultsHandler() + { + Handle = obj => + { + // clone the object and reduce the attributes only the set of + // attributes. + return _handler.Handle(ReduceToAttrsToGet(obj)); + } + }; + } + } + } + #endregion + + #region SubscriptionImpl + + public class ConnectorEventSubscriptionApiOpImp : SubscriptionImpl, IConnectorEventSubscriptionApiOp + { + + /// + /// Creates the API operation so it can called multiple times. + /// + /// + /// + /// + public ConnectorEventSubscriptionApiOpImp(ConnectorOperationalContext context, Connector connector, LocalConnectorFacadeImpl.ReferenceCounter referenceCounter) + : base(context, connector, referenceCounter) + { + } + + public ISubscription Subscribe(ObjectClass objectClass, Filter eventFilter, IObserver handler, OperationOptions operationOptions) + { + Assertions.NullCheck(objectClass, "objectClass"); + Assertions.NullCheck(handler, "handler"); + //convert null into empty + if (operationOptions == null) + { + operationOptions = new OperationOptionsBuilder().Build(); + } + IConnectorEventSubscriptionOp operation = ((IConnectorEventSubscriptionOp)GetConnector()); + InternalObserver observer = new InternalObserver(handler, ReferenceCounter); + try + { + ReferenceCounter.Acquire(); + return observer.Subscription(operation.Subscribe(objectClass, eventFilter, observer, operationOptions)); + } + catch (Exception e) + { + observer.OnError(e); + throw; + } + } + } + + public class SyncEventSubscriptionApiOpImpl : SubscriptionImpl, ISyncEventSubscriptionApiOp + { + + /// + /// Creates the API operation so it can called multiple times. + /// + /// + /// + /// + public SyncEventSubscriptionApiOpImpl(ConnectorOperationalContext context, Connector connector, LocalConnectorFacadeImpl.ReferenceCounter referenceCounter) + : base(context, connector, referenceCounter) + { + } + + public ISubscription Subscribe(ObjectClass objectClass, SyncToken token, IObserver handler, OperationOptions operationOptions) + { + Assertions.NullCheck(objectClass, "objectClass"); + Assertions.NullCheck(handler, "handler"); + //convert null into empty + if (operationOptions == null) + { + operationOptions = new OperationOptionsBuilder().Build(); + } + + ISyncEventSubscriptionOp operation = ((ISyncEventSubscriptionOp)GetConnector()); + InternalObserver observer = new InternalObserver(handler, ReferenceCounter); + try + { + ReferenceCounter.Acquire(); + return observer.Subscription(operation.Subscribe(objectClass, token, observer, operationOptions)); + } + catch (Exception e) + { + observer.OnError(e); + throw; + } + } + } + public class SubscriptionImpl : ConnectorAPIOperationRunner + { + + protected readonly LocalConnectorFacadeImpl.ReferenceCounter ReferenceCounter; + + /// + /// Creates the API operation so it can called multiple times. + /// + /// + /// + protected internal SubscriptionImpl(ConnectorOperationalContext context, Connector connector, LocalConnectorFacadeImpl.ReferenceCounter referenceCounter) + : base(context, connector) + { + ReferenceCounter = referenceCounter; + } + + + protected sealed class InternalObserver : IObserver + { + private readonly IObserver _observer; + private readonly CancellationSubscription _subscribed = new CancellationSubscription(); + + public InternalObserver(IObserver observer, LocalConnectorFacadeImpl.ReferenceCounter referenceCounter) + { + _observer = observer; + _subscribed.Token.Register(referenceCounter.Release); + } + + public CancellationSubscription Subscription(ISubscription subscription) + { + Assertions.NullCheck(subscription, "subscription"); + _subscribed.Token.Register(subscription.Dispose); + return _subscribed; + } + + public void OnCompleted() + { + if (!_subscribed.Unsubscribed) + { + try + { + _subscribed.Dispose(); + } + finally + { + _observer.OnCompleted(); + } + } + } + + public void OnError(Exception e) + { + if (!_subscribed.Unsubscribed) + { + try + { + _subscribed.Dispose(); + } + finally + { + _observer.OnError(e); + } + } + } + + public void OnNext(T connectorObject) + { + try + { + if (!_subscribed.Unsubscribed) + { + _observer.OnNext(connectorObject); + } + } + catch (Exception t) + { + OnError(t); + } + } + } + } + #endregion + + #region SyncAttributesToGetResultsHandler + public sealed class SyncAttributesToGetResultsHandler : + AttributesToGetResultsHandler + { + // ======================================================================= + // Fields + // ======================================================================= + private readonly SyncResultsHandler _handler; + + // ======================================================================= + // Constructors + // ======================================================================= + public SyncAttributesToGetResultsHandler( + SyncResultsHandler handler, string[] attrsToGet) + : base(attrsToGet) + { + Assertions.NullCheck(handler, "handler"); + this._handler = handler; + } + + public SyncResultsHandler SyncResultsHandler + { + get + { + return new SyncResultsHandler() + { + Handle = delta => + { + SyncDeltaBuilder bld = new SyncDeltaBuilder(); + bld.Uid = delta.Uid; + bld.Token = delta.Token; + bld.DeltaType = delta.DeltaType; + if (delta.Object != null) + { + bld.Object = ReduceToAttrsToGet(delta.Object); + } + return _handler.Handle(bld.Build()); + } + }; + } + } + } + #endregion + + #region DuplicateFilteringResultsHandler + public sealed class DuplicateFilteringResultsHandler + { + // ======================================================================= + // Fields + // ======================================================================= + private readonly SearchResultsHandler _handler; + private readonly HashSet _visitedUIDs = new HashSet(); + + private bool _stillHandling = true; + + // ======================================================================= + // Constructors + // ======================================================================= + /// + /// Filter chain for producers. + /// + /// Producer to filter. + public DuplicateFilteringResultsHandler(SearchResultsHandler handler) + { + // there must be a producer.. + if (handler == null) + { + throw new ArgumentException("Handler must not be null!"); + } + this._handler = handler; + } + + public SearchResultsHandler ResultsHandler + { + get + { + return new SearchResultsHandler() + { + Handle = obj => + { + String uid = + obj.Uid.GetUidValue(); + if (!_visitedUIDs.Add(uid)) + { + //we've already seen this - don't pass it + //throw + return true; + } + _stillHandling = _handler.Handle(obj); + return _stillHandling; + }, + HandleResult = result => + { + _handler.HandleResult(result); + } + + }; + } + + } + + public bool IsStillHandling + { + get + { + return _stillHandling; + } + } + } + #endregion + + #region FilteredResultsHandler + public sealed class FilteredResultsHandler + { + // ======================================================================= + // Fields + // ======================================================================= + readonly ResultsHandler handler; + readonly Filter filter; + + // ======================================================================= + // Constructors + // ======================================================================= + /// + /// Filter chain for producers. + /// + /// Producer to filter. + /// Filter to use to accept objects. + public FilteredResultsHandler(ResultsHandler handler, Filter filter) + { + // there must be a producer.. + if (handler == null) + { + throw new ArgumentException("Producer must not be null!"); + } + this.handler = handler; + // use a default pass through filter.. + this.filter = filter == null ? new PassThroughFilter() : filter; + } + + public ResultsHandler ResultsHandler + { + get + { + return new ResultsHandler + { + Handle = obj => + { + if (filter.Accept(obj)) + { + return handler.Handle(obj); + } + else + { + return true; + } + } + }; + } + } + + /// + /// Use a pass through filter to use if a null filter is provided. + /// + class PassThroughFilter : Filter + { + public bool Accept(ConnectorObject obj) + { + return true; + } + + public R Accept(FilterVisitor v, P p) + { + return v.VisitExtendedFilter(p, this); + } + } + } + #endregion + + #region GetImpl + /// + /// Uses to find the object that is referenced by the + /// provided. + /// + public class GetImpl : GetApiOp + { + readonly SearchApiOp op; + + private class ResultAdapter + { + private IList _list = new List(); + public ResultsHandler ResultsHandler + { + get + { + return new ResultsHandler() + { + Handle = obj => + { + _list.Add(obj); + return false; + } + }; + } + } + public ConnectorObject GetResult() + { + return _list.Count == 0 ? null : _list[0]; + } + } + + public GetImpl(SearchApiOp search) + { + this.op = search; + } + + public ConnectorObject GetObject(ObjectClass objectClass, Uid uid, OperationOptions options) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new System.NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(uid, "uid"); + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + Filter filter = FilterBuilder.EqualTo(uid); + ResultAdapter adapter = new ResultAdapter(); + op.Search(objectClass, filter, adapter.ResultsHandler, options); + return adapter.GetResult(); + } + } + #endregion + + #region OperationalContext + /// + /// NOTE: internal class, public only for unit tests + /// OperationalContext - base class for operations that do not require a + /// connector instance. + /// + public class OperationalContext : AbstractConfiguration.IConfigurationChangeCallback + { + + /// + /// ConnectorInfo + /// + protected readonly LocalConnectorInfoImpl connectorInfo; + + /// + /// Contains the . + /// + protected readonly APIConfigurationImpl apiConfiguration; + + private volatile Configuration configuration; + + /// + /// Creates a new OperationalContext but it does not initiates the + /// Configuration because the method must do it + /// when it's called from a block where the classloader of the Thread is set + /// to Connector. + /// + /// + /// + public OperationalContext(LocalConnectorInfoImpl connectorInfo, APIConfigurationImpl apiConfiguration) + { + this.connectorInfo = connectorInfo; + this.apiConfiguration = apiConfiguration; + } + + /* + * This method must be called when the Bundle ClassLoader is the Thread + * Context ClassLoader. + */ + public Configuration GetConfiguration() + { + + if (null == configuration) + { + lock (this) + { + if (null == configuration) + { + configuration = + CSharpClassProperties.CreateBean( + (ConfigurationPropertiesImpl) apiConfiguration.ConfigurationProperties, + connectorInfo.ConnectorConfigurationClass); + if (null != apiConfiguration.ChangeListener + && configuration is AbstractConfiguration) + { + ((AbstractConfiguration) configuration).AddChangeCallback(this); + } + } + } + } + return configuration; + } + + protected LocalConnectorInfoImpl GetConnectorInfo() + { + + return connectorInfo; + + } + + public ResultsHandlerConfiguration getResultsHandlerConfiguration() + { + return new ResultsHandlerConfiguration(apiConfiguration.ResultsHandlerConfiguration); + } + + public virtual void Dispose() + { + if (configuration is StatefulConfiguration) + { + // dispose it not supposed to throw, but just in case, + // catch the exception and log it so we know about it + // but don't let the exception prevent additional + // cleanup that needs to happen + try + { + StatefulConfiguration config = (StatefulConfiguration)configuration; + configuration = null; + config.Release(); + } + catch (Exception e) + { + // log this though + Trace.TraceWarning(e.Message); + } + } + } + + public void NotifyUpdate() + { + try + { + IConfigurationPropertyChangeListener listener = apiConfiguration.ChangeListener; + if (null != listener) + { + IList diff = + CSharpClassProperties.CalculateDiff(apiConfiguration.ConfigurationProperties, configuration); + if (diff.Count > 0) + { + listener.ConfigurationPropertyChange(diff); + } + } + } + catch (Exception e) + { + TraceUtil.TraceException("Configuration change notification is failed for" + configuration.GetType(), e); + } + } + } + #endregion + + #region NormalizingResultsHandler + public class NormalizingResultsHandler + { + private readonly ResultsHandler _target; + private readonly ObjectNormalizerFacade _normalizer; + + public NormalizingResultsHandler(ResultsHandler target, + ObjectNormalizerFacade normalizer) + { + Assertions.NullCheck(target, "target"); + Assertions.NullCheck(normalizer, "normalizer"); + _target = target; + _normalizer = normalizer; + } + + public ResultsHandler ResultsHandler + { + get + { + return new ResultsHandler() + { + + Handle = obj => + { + ConnectorObject normalized = _normalizer.NormalizeObject(obj); + return _target.Handle(normalized); + } + }; + } + } + } + #endregion + + #region NormalizingSyncResultsHandler + public class NormalizingSyncResultsHandler + { + private readonly SyncResultsHandler _target; + private readonly ObjectNormalizerFacade _normalizer; + + public NormalizingSyncResultsHandler(SyncResultsHandler target, + ObjectNormalizerFacade normalizer) + { + Assertions.NullCheck(target, "target"); + Assertions.NullCheck(normalizer, "normalizer"); + _target = target; + _normalizer = normalizer; + } + + public SyncResultsHandler SyncResultsHandler + { + get + { + return new SyncResultsHandler() + { + + Handle = delta => + { + SyncDelta normalized = _normalizer.NormalizeSyncDelta(delta); + return _target.Handle(normalized); + } + }; + } + } + } + #endregion + + #region CaseNormalizer + public sealed class CaseNormalizer : AttributeNormalizer + { + public static ObjectNormalizerFacade CreateCaseNormalizerFacade(ObjectClass oclass) + { + return new ObjectNormalizerFacade(oclass, new CaseNormalizer()); + } + + public ConnectorAttribute NormalizeAttribute(ObjectClass oclass, ConnectorAttribute attribute) + { + // Trace.TraceInformation("Starting CaseNormalizer.NormalizeAttribute({0}, {1})", oclass, attribute.GetDetails()); + ConnectorAttribute rv = attribute; + bool converted = false; + + IList values = rv.Value; + if (values != null) + { + IList newValues = new List(); + + foreach (object value in values) + { + if (value is string) + { + newValues.Add(((string)value).ToUpper()); + converted = true; + } + else + { + newValues.Add(value); + } + } + + if (converted) // only when something changed; to save a few cpu cycles... + { + rv = ConnectorAttributeBuilder.Build(attribute.Name, newValues); + } + } + + // Trace.TraceInformation("Finishing CaseNormalizer.NormalizeAttribute, converted = {0}, return value = {1}", converted, rv.GetDetails()); + return rv; + } + } + #endregion + + #region NormalizingFilter + /// + /// Proxy the filter to filter based on object normalized version. + /// Similar to ObjectNormalizerFacade.NormalizeFilter, + /// but this one DOES NOT expect that it gets object to be accepted/rejected + /// in normalized form - it normalizes the object just before deciding. + /// Currently used for case insensitive filtering. + /// + public sealed class NormalizingFilter : ExternallyChainedFilter + { + private readonly ObjectNormalizerFacade _normalizationFacade; + + public NormalizingFilter(Filter filter, ObjectNormalizerFacade facade) + : base(facade.NormalizeFilter(filter)) + { + _normalizationFacade = facade; + } + + /// + /// Return the decision based on normalized version of the object. + /// + /// + public override bool Accept(ConnectorObject obj) + { + bool result = Filter.Accept(_normalizationFacade.NormalizeObject(obj)); + // Trace.TraceInformation("NormalizingFilter.Accept returns {0} for {1}", result, obj.GetAttributeByName("__NAME__")); + return result; + } + + public override R Accept(FilterVisitor v, P p) + { + return v.VisitExtendedFilter(p, this); + } + + public override string ToString() + { + StringBuilder bld = new StringBuilder(); + bld.Append("NORMALIZE USING ").Append(_normalizationFacade).Append(": ").Append(Filter); + return bld.ToString(); + } + } + #endregion + + #region ObjectNormalizerFacade + public sealed class ObjectNormalizerFacade + { + /// + /// The (non-null) object class + /// + private readonly ObjectClass _objectClass; + /// + /// The (possibly null) attribute normalizer + /// + private readonly AttributeNormalizer _normalizer; + + /// + /// Create a new ObjectNormalizer + /// + /// The object class + /// The normalizer. May be null. + public ObjectNormalizerFacade(ObjectClass objectClass, + AttributeNormalizer normalizer) + { + Assertions.NullCheck(objectClass, "objectClass"); + _objectClass = objectClass; + _normalizer = normalizer; + } + + /// + /// Returns the normalized value of the attribute. + /// + /// + /// If no normalizer is specified, returns the original + /// attribute. + /// + /// The attribute to normalize. + /// The normalized attribute + public ConnectorAttribute NormalizeAttribute(ConnectorAttribute attribute) + { + if (attribute == null) + { + return null; + } + else if (_normalizer != null) + { + return _normalizer.NormalizeAttribute(_objectClass, attribute); + } + else + { + return attribute; + } + } + + /// + /// Returns the normalized set of attributes or null + /// if the original set is null. + /// + /// The original attributes. + /// The normalized attributes or null if + /// the original set is null. + public ICollection NormalizeAttributes(ICollection attributes) + { + if (attributes == null) + { + return null; + } + ICollection temp = new HashSet(); + foreach (ConnectorAttribute attribute in attributes) + { + temp.Add(NormalizeAttribute(attribute)); + } + return CollectionUtil.AsReadOnlySet(temp); + } + + /// + /// Returns the normalized object. + /// + /// The original object + /// The normalized object. + public ConnectorObject NormalizeObject(ConnectorObject orig) + { + return new ConnectorObject(orig.ObjectClass, + NormalizeAttributes(orig.GetAttributes())); + } + + /// + /// Returns the normalized sync delta. + /// + /// The original delta. + /// The normalized delta. + public SyncDelta NormalizeSyncDelta(SyncDelta delta) + { + SyncDeltaBuilder builder = new + SyncDeltaBuilder(delta); + if (delta.Object != null) + { + builder.Object = NormalizeObject(delta.Object); + } + return builder.Build(); + } + + /// + /// Returns a filter consisting of the original with + /// all attributes normalized. + /// + /// The original. + /// The normalized filter. + public Filter NormalizeFilter(Filter filter) + { + if (filter is ContainsFilter) + { + AttributeFilter afilter = + (AttributeFilter)filter; + return new ContainsFilter(NormalizeAttribute(afilter.GetAttribute())); + } + else if (filter is EndsWithFilter) + { + AttributeFilter afilter = + (AttributeFilter)filter; + return new EndsWithFilter(NormalizeAttribute(afilter.GetAttribute())); + } + else if (filter is EqualsFilter) + { + AttributeFilter afilter = + (AttributeFilter)filter; + return new EqualsFilter(NormalizeAttribute(afilter.GetAttribute())); + } + else if (filter is GreaterThanFilter) + { + AttributeFilter afilter = + (AttributeFilter)filter; + return new GreaterThanFilter(NormalizeAttribute(afilter.GetAttribute())); + } + else if (filter is GreaterThanOrEqualFilter) + { + AttributeFilter afilter = + (AttributeFilter)filter; + return new GreaterThanOrEqualFilter(NormalizeAttribute(afilter.GetAttribute())); + } + else if (filter is LessThanFilter) + { + AttributeFilter afilter = + (AttributeFilter)filter; + return new LessThanFilter(NormalizeAttribute(afilter.GetAttribute())); + } + else if (filter is LessThanOrEqualFilter) + { + AttributeFilter afilter = + (AttributeFilter)filter; + return new LessThanOrEqualFilter(NormalizeAttribute(afilter.GetAttribute())); + } + else if (filter is StartsWithFilter) + { + AttributeFilter afilter = + (AttributeFilter)filter; + return new StartsWithFilter(NormalizeAttribute(afilter.GetAttribute())); + } + else if (filter is ContainsAllValuesFilter) + { + AttributeFilter afilter = + (AttributeFilter)filter; + return new ContainsAllValuesFilter(NormalizeAttribute(afilter.GetAttribute())); + } + else if (filter is NotFilter) + { + NotFilter notFilter = + (NotFilter)filter; + return new NotFilter(NormalizeFilter(notFilter.Filter)); + } + else if (filter is AndFilter) + { + AndFilter andFilter = + (AndFilter)filter; + return new AndFilter(NormalizeFilter(andFilter.Left), + NormalizeFilter(andFilter.Right)); + } + else if (filter is OrFilter) + { + OrFilter orFilter = + (OrFilter)filter; + return new OrFilter(NormalizeFilter(orFilter.Left), + NormalizeFilter(orFilter.Right)); + } + else + { + return filter; + } + } + } + #endregion + + #region SchemaImpl + internal class SchemaImpl : ConnectorAPIOperationRunner, SchemaApiOp + { + /// + /// Initializes the operation works. + /// + public SchemaImpl(ConnectorOperationalContext context, + Connector connector) + : base(context, connector) + { + } + + /// + /// Retrieve the schema from the . + /// + /// + public Schema Schema() + { + return ((SchemaOp)GetConnector()).Schema(); + } + } + #endregion + + #region ScriptOnConnectorImpl + public class ScriptOnConnectorImpl : ConnectorAPIOperationRunner, + ScriptOnConnectorApiOp + { + public ScriptOnConnectorImpl(ConnectorOperationalContext context, + Connector connector) : + base(context, connector) + { + } + + public Object RunScriptOnConnector(ScriptContext request, + OperationOptions options) + { + Assertions.NullCheck(request, "request"); + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + Object rv; + if (GetConnector() is ScriptOnConnectorOp) + { + rv = ((ScriptOnConnectorOp)GetConnector()).RunScriptOnConnector(request, options); + } + else + { + String language = request.ScriptLanguage; + Assembly assembly = GetConnector().GetType().Assembly; + + ScriptExecutor executor = + ScriptExecutorFactory.NewInstance(language).NewScriptExecutor( + BuildReferenceList(assembly), + request.ScriptText, + false); + IDictionary scriptArgs = + new Dictionary(request.ScriptArguments); + scriptArgs["connector"] = GetConnector(); //add the connector instance itself + rv = executor.Execute(scriptArgs); + } + return SerializerUtil.CloneObject(rv); + } + + private Assembly[] BuildReferenceList(Assembly assembly) + { + List list = new List(); + foreach (var assemblyName in assembly.GetReferencedAssemblies()) + { + list.Add(Assembly.Load(assemblyName)); + } + // Just add the connector itself. + list.Add(assembly); + return list.ToArray(); + } + } + #endregion + + #region ScriptOnResourceImpl + public class ScriptOnResourceImpl : ConnectorAPIOperationRunner, + ScriptOnResourceApiOp + { + public ScriptOnResourceImpl(ConnectorOperationalContext context, + Connector connector) : + base(context, connector) + { + } + + public Object RunScriptOnResource(ScriptContext request, + OperationOptions options) + { + Assertions.NullCheck(request, "request"); + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + Object rv + = ((ScriptOnResourceOp)GetConnector()).RunScriptOnResource(request, options); + return SerializerUtil.CloneObject(rv); + } + + } + #endregion + + #region SearchImpl + internal class SearchImpl : ConnectorAPIOperationRunner, SearchApiOp + { + /// + /// Initializes the operation works. + /// + public SearchImpl(ConnectorOperationalContext context, + Connector connector) + : base(context, connector) + { + } + + /// + /// Call the SPI search routines to return the results to the + /// . + /// + /// + public SearchResult Search(ObjectClass objectClass, Filter originalFilter, ResultsHandler handler, OperationOptions options) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new System.NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(handler, "handler"); + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + + ResultsHandlerConfiguration hdlCfg = null != GetOperationalContext() ? + GetOperationalContext().getResultsHandlerConfiguration() : new ResultsHandlerConfiguration(); + ResultsHandler handlerChain = handler; + Filter actualFilter = originalFilter; // actualFilter is used for chaining filters - it points to the filter where new filters should be chained + + if (hdlCfg.EnableFilteredResultsHandler && hdlCfg.EnableCaseInsensitiveFilter && actualFilter != null) + { + Trace.TraceInformation("Creating case insensitive filter"); + ObjectNormalizerFacade normalizer = CaseNormalizer.CreateCaseNormalizerFacade(objectClass); + actualFilter = new NormalizingFilter(actualFilter, normalizer); + } + + if (hdlCfg.EnableNormalizingResultsHandler) + { + ObjectNormalizerFacade normalizer = GetNormalizer(objectClass); + //chain a normalizing handler (must come before + //filter handler) + ResultsHandler normalizingHandler = new NormalizingResultsHandler(handler, normalizer).ResultsHandler; + // chain a filter handler.. + if (hdlCfg.EnableFilteredResultsHandler) + { + // chain a filter handler.. + Filter normalizedFilter = normalizer.NormalizeFilter(actualFilter); + handlerChain = new FilteredResultsHandler(normalizingHandler, normalizedFilter).ResultsHandler; + actualFilter = normalizedFilter; + } + else + { + handlerChain = normalizingHandler; + } + } + else if (hdlCfg.EnableFilteredResultsHandler) + { + // chain a filter handler.. + handlerChain = new FilteredResultsHandler(handlerChain, actualFilter).ResultsHandler; + } + + //get the IList interface that this type implements + Type interfaceType = ReflectionUtil.FindInHierarchyOf + (typeof(SearchOp<>), GetConnector().GetType()); + Type[] val = interfaceType.GetGenericArguments(); + if (val.Length != 1) + { + throw new Exception("Unexpected type: " + interfaceType); + } + Type queryType = val[0]; + Type searcherRawType = typeof(RawSearcherImpl<>); + Type searcherType = + searcherRawType.MakeGenericType(queryType); + RawSearcher searcher = (RawSearcher)Activator.CreateInstance(searcherType); + + // add attributes to get handler + string[] attrsToGet = options.AttributesToGet; + if (attrsToGet != null && attrsToGet.Length > 0 && hdlCfg.EnableAttributesToGetSearchResultsHandler) + { + handlerChain = new SearchAttributesToGetResultsHandler( + handlerChain, attrsToGet).ResultsHandler; + } + SearchResult result = null; + SearchResultsHandler innreHandler = new SearchResultsHandler() + { + Handle = obj => + { + return handlerChain.Handle(obj); + }, + + HandleResult = obj => + { + result = obj; + } + }; + searcher.RawSearch(GetConnector(), objectClass, actualFilter, innreHandler, options); + return result; + } + } + #endregion + + #region RawSearcher + internal interface RawSearcher + { + /// + /// Public because it is used by TestHelpers. + /// + /// + /// Raw, + /// SPI-level search. + /// + /// The underlying implementation of search + /// (generally the connector itself) + /// The object class + /// The filter + /// The handler + /// The options + void RawSearch(Object search, + ObjectClass oclass, + Filter filter, + SearchResultsHandler handler, + OperationOptions options); + } + #endregion + + #region RawSearcherImpl + internal class RawSearcherImpl : RawSearcher where T : class + { + public void RawSearch(Object search, + ObjectClass oclass, + Filter filter, + SearchResultsHandler handler, + OperationOptions options) + { + RawSearch((SearchOp)search, oclass, filter, handler, options); + } + + /// + /// Public because it is used by TestHelpers. + /// + /// + /// Raw, + /// SPI-level search. + /// + /// The underlying implementation of search + /// (generally the connector itself) + /// The object class + /// The filter + /// The handler + /// The options + public static void RawSearch(SearchOp search, + ObjectClass oclass, + Filter filter, + SearchResultsHandler handler, + OperationOptions options) + { + FilterTranslator translator = + search.CreateFilterTranslator(oclass, options); + IList queries = + (IList)translator.Translate(filter); + if (queries.Count == 0) + { + search.ExecuteQuery(oclass, + null, handler, options); + } + else + { + //eliminate dups if more than one + bool eliminateDups = queries.Count > 1; + DuplicateFilteringResultsHandler dups = null; + if (eliminateDups) + { + dups = new DuplicateFilteringResultsHandler(handler); + handler = dups.ResultsHandler; + } + foreach (T query in queries) + { + search.ExecuteQuery(oclass, + query, handler, options); + //don't run any more queries if the consumer + //has stopped + if (dups != null) + { + if (!dups.IsStillHandling) + { + break; + } + } + } + } + } + + } + #endregion + + #region SyncImpl + public class SyncImpl : ConnectorAPIOperationRunner, + SyncApiOp + { + public SyncImpl(ConnectorOperationalContext context, + Connector connector) + : base(context, connector) + { + } + + public SyncToken Sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler, OperationOptions options) + { + //token is allowed to be null, objClass and handler must not be null + Assertions.NullCheck(objectClass, "objectClass"); + Assertions.NullCheck(handler, "handler"); + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + // add a handler in the chain to remove attributes + string[] attrsToGet = options.AttributesToGet; + if (attrsToGet != null && attrsToGet.Length > 0) + { + handler = new SyncAttributesToGetResultsHandler(handler, attrsToGet).SyncResultsHandler; + } + //chain a normalizing results handler + if (GetConnector() is AttributeNormalizer) + { + handler = new NormalizingSyncResultsHandler(handler, GetNormalizer(objectClass)).SyncResultsHandler; + } + + SyncToken result = null; + Boolean doAll = ObjectClass.ALL.Equals(objectClass); + ((SyncOp)GetConnector()).Sync(objectClass, token, new SyncTokenResultsHandler() + { + Handle = delta => + { + if (doAll && SyncDeltaType.DELETE.Equals(delta.DeltaType) + && null == delta.ObjectClass) + { + throw new ConnectorException( + "Sync '__ALL__' operation requires the connector to set 'objectClass' parameter for sync event."); + } + return handler.Handle(delta); + }, + HandleResult = obj => + { + result = obj; + } + }, options); + return result; + } + + public SyncToken GetLatestSyncToken(ObjectClass objectClass) + { + Assertions.NullCheck(objectClass, "objectClass"); + return ((SyncOp)GetConnector()).GetLatestSyncToken(objectClass); + } + } + #endregion + + #region TestImpl + /// + /// Provides a method for the API to call the SPI's test method on the + /// connector. + /// + /// + /// The test method is intended to determine if the + /// is ready to perform the various operations it supports. + /// + /// Will Droste + internal class TestImpl : ConnectorAPIOperationRunner, TestApiOp + { + public TestImpl(ConnectorOperationalContext context, Connector connector) + : base(context, connector) + { + } + + public void Test() + { + ((TestOp)GetConnector()).Test(); + } + + } + #endregion + + #region UpdateImpl + /// + /// NOTE: internal class, public only for unit tests + /// Handles both version of update this include simple replace and the advance + /// update. + /// + public class UpdateImpl : ConnectorAPIOperationRunner, UpdateApiOp + { + /// + /// All the operational attributes that can not be added or deleted. + /// + static readonly HashSet OPERATIONAL_ATTRIBUTE_NAMES = new HashSet(); + + const String OPERATIONAL_ATTRIBUTE_ERR = + "Operational attribute '{0}' can not be added or deleted only replaced."; + + static UpdateImpl() + { + OPERATIONAL_ATTRIBUTE_NAMES.Add(Name.NAME); + CollectionUtil.AddAll(OPERATIONAL_ATTRIBUTE_NAMES, + OperationalAttributes.OPERATIONAL_ATTRIBUTE_NAMES); + } + + /// + /// Determines which type of update a connector supports and then uses that + /// handler. + /// + public UpdateImpl(ConnectorOperationalContext context, + Connector connector) + : base(context, connector) + { + } + + public Uid Update(ObjectClass objclass, + Uid uid, + ICollection replaceAttributes, + OperationOptions options) + { + // validate all the parameters.. + ValidateInput(objclass, uid, replaceAttributes, false); + //cast null as empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + + ObjectNormalizerFacade normalizer = + GetNormalizer(objclass); + uid = (Uid)normalizer.NormalizeAttribute(uid); + replaceAttributes = + normalizer.NormalizeAttributes(replaceAttributes); + UpdateOp op = (UpdateOp)GetConnector(); + Uid ret = op.Update(objclass, uid, replaceAttributes, options); + return (Uid)normalizer.NormalizeAttribute(ret); + } + + public Uid AddAttributeValues(ObjectClass objectClass, + Uid uid, + ICollection valuesToAdd, + OperationOptions options) + { + // validate all the parameters.. + ValidateInput(objectClass, uid, valuesToAdd, true); + //cast null as empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + + ObjectNormalizerFacade normalizer = + GetNormalizer(objectClass); + uid = (Uid)normalizer.NormalizeAttribute(uid); + valuesToAdd = + normalizer.NormalizeAttributes(valuesToAdd); + UpdateOp op = (UpdateOp)GetConnector(); + Uid ret; + if (op is UpdateAttributeValuesOp) + { + UpdateAttributeValuesOp valueOp = + (UpdateAttributeValuesOp)op; + ret = valueOp.AddAttributeValues(objectClass, uid, valuesToAdd, options); + } + else + { + ICollection replaceAttributes = + FetchAndMerge(objectClass, uid, valuesToAdd, true, options); + ret = op.Update(objectClass, uid, replaceAttributes, options); + } + return (Uid)normalizer.NormalizeAttribute(ret); + } + + public Uid RemoveAttributeValues(ObjectClass objectClass, + Uid uid, + ICollection valuesToRemove, + OperationOptions options) + { + // validate all the parameters.. + ValidateInput(objectClass, uid, valuesToRemove, true); + //cast null as empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + + ObjectNormalizerFacade normalizer = + GetNormalizer(objectClass); + uid = (Uid)normalizer.NormalizeAttribute(uid); + valuesToRemove = + normalizer.NormalizeAttributes(valuesToRemove); + UpdateOp op = (UpdateOp)GetConnector(); + Uid ret; + if (op is UpdateAttributeValuesOp) + { + UpdateAttributeValuesOp valueOp = + (UpdateAttributeValuesOp)op; + ret = valueOp.RemoveAttributeValues(objectClass, uid, valuesToRemove, options); + } + else + { + ICollection replaceAttributes = + FetchAndMerge(objectClass, uid, valuesToRemove, false, options); + ret = op.Update(objectClass, uid, replaceAttributes, options); + } + return (Uid)normalizer.NormalizeAttribute(ret); + } + + private ICollection FetchAndMerge(ObjectClass objclass, Uid uid, + ICollection valuesToChange, + bool add, + OperationOptions options) + { + // check that this connector supports Search.. + if (ReflectionUtil.FindInHierarchyOf(typeof(SearchOp<>), GetConnector().GetType()) == null) + { + String MSG = "Connector must support search"; + throw new InvalidOperationException(MSG); + } + + //add attrs to get to operation options, so that the + //object we fetch has exactly the set of attributes we require + //(there may be ones that are not in the default set) + OperationOptionsBuilder builder = new OperationOptionsBuilder(options); + ICollection attrNames = new HashSet(); + foreach (ConnectorAttribute attribute in valuesToChange) + { + attrNames.Add(attribute.Name); + } + builder.AttributesToGet = (attrNames.ToArray()); + options = builder.Build(); + + // get the connector object from the resource... + ConnectorObject o = GetConnectorObject(objclass, uid, options); + if (o == null) + { + throw new UnknownUidException(uid, objclass); + } + // merge the update data.. + ICollection mergeAttrs = Merge(valuesToChange, o.GetAttributes(), add); + return mergeAttrs; + } + + /// + /// Merges two connector objects into a single updated object. + /// + public ICollection Merge(ICollection updateAttrs, + ICollection baseAttrs, bool add) + { + // return the merged attributes + ICollection ret = new HashSet(); + // create map that can be modified to get the subset of changes + IDictionary baseAttrMap = ConnectorAttributeUtil.ToMap(baseAttrs); + // run through attributes of the current object.. + foreach (ConnectorAttribute updateAttr in updateAttrs) + { + // get the name of the update attributes + String name = updateAttr.Name; + // remove each attribute that is an update attribute.. + ConnectorAttribute baseAttr = CollectionUtil.GetValue(baseAttrMap, name, null); + IList values; + ConnectorAttribute modifiedAttr; + if (add) + { + if (baseAttr == null) + { + modifiedAttr = updateAttr; + } + else + { + // create a new list with the base attribute to add to.. + values = CollectionUtil.NewList(baseAttr.Value); + CollectionUtil.AddAll(values, updateAttr.Value); + modifiedAttr = ConnectorAttributeBuilder.Build(name, values); + } + } + else + { + if (baseAttr == null) + { + // nothing to actually do the attribute do not exist + continue; + } + else + { + // create a list with the base attribute to remove from.. + values = CollectionUtil.NewList(baseAttr.Value); + foreach (Object val in updateAttr.Value) + { + values.Remove(val); + } + // if the values are empty send a null to the connector.. + if (values.Count == 0) + { + modifiedAttr = ConnectorAttributeBuilder.Build(name); + } + else + { + modifiedAttr = ConnectorAttributeBuilder.Build(name, values); + } + } + } + ret.Add(modifiedAttr); + } + return ret; + } + + /// + /// Get the to modify. + /// + private ConnectorObject GetConnectorObject(ObjectClass oclass, Uid uid, OperationOptions options) + { + // attempt to get the connector object.. + GetApiOp get = new GetImpl(new SearchImpl((ConnectorOperationalContext)GetOperationalContext(), + GetConnector())); + return get.GetObject(oclass, uid, options); + } + + /// + /// Makes things easier if you can trust the input. + /// + public static void ValidateInput(ObjectClass objectClass, + Uid uid, + ICollection replaceAttributes, bool isDelta) + { + Assertions.NullCheck(uid, "uid"); + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new System.NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(replaceAttributes, "replaceAttributes"); + // check to make sure there's not a uid.. + if (ConnectorAttributeUtil.GetUidAttribute(replaceAttributes) != null) + { + throw new ArgumentException( + "Parameter 'attrs' contains a uid."); + } + // check for things only valid during ADD/DELETE + if (isDelta) + { + foreach (ConnectorAttribute attr in replaceAttributes) + { + Assertions.NullCheck(attr, "attr"); + // make sure that none of the values are null.. + if (attr.Value == null) + { + throw new ArgumentException( + "Can not add or remove a 'null' value."); + } + // make sure that if this an delete/add that it doesn't include + // certain attributes because it doesn't make any sense.. + String name = attr.Name; + if (OPERATIONAL_ATTRIBUTE_NAMES.Contains(name)) + { + String msg = String.Format(OPERATIONAL_ATTRIBUTE_ERR, name); + throw new ArgumentException(msg); + } + } + } + } + } + #endregion + + #region ValidateImpl + internal class ValidateImpl : APIOperationRunner, ValidateApiOp + { + + public ValidateImpl(OperationalContext context) + : base(context) + { + } + + public void Validate() + { + GetOperationalContext().GetConfiguration().Validate(); + } + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/ApiRemote.cs b/dotnet/framework/FrameworkInternal/ApiRemote.cs new file mode 100644 index 00000000..b822a23b --- /dev/null +++ b/dotnet/framework/FrameworkInternal/ApiRemote.cs @@ -0,0 +1,698 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Reflection; +using System.Security.Authentication; +using System.Globalization; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Proxy; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Impl.Api.Remote.Messages; + +namespace Org.IdentityConnectors.Framework.Impl.Api.Remote +{ + #region RemoteFrameworkConnection + public class RemoteFrameworkConnection : IDisposable + { + private TcpClient _socket; + private Stream _stream; + + private BinaryObjectSerializer _encoder; + private BinaryObjectDeserializer _decoder; + + public RemoteFrameworkConnection(RemoteFrameworkConnectionInfo info) + { + Init(info); + } + + public RemoteFrameworkConnection(TcpClient socket, Stream stream) + { + Init(socket, stream); + } + + private void Init(RemoteFrameworkConnectionInfo connectionInfo) + { + IPAddress[] addresses = + Dns.GetHostAddresses(connectionInfo.Host); + TcpClient client = new TcpClient(addresses[0].AddressFamily); + client.SendTimeout = connectionInfo.Timeout; + client.ReceiveTimeout = connectionInfo.Timeout; + client.Connect(addresses[0], connectionInfo.Port); + Stream stream; + try + { + stream = client.GetStream(); + } + catch (Exception) + { + try { client.Close(); } + catch (Exception) { } + throw; + } + try + { + if (connectionInfo.UseSSL) + { + if (connectionInfo.CertificateValidationCallback != null) + { + RemoteCertificateValidationCallback callback = + connectionInfo.CertificateValidationCallback; + + stream = new SslStream( + stream, false, callback); + } + else + { + stream = new SslStream(stream, + false); + } + ((SslStream)stream).AuthenticateAsClient(connectionInfo.Host, + new X509CertificateCollection(new X509Certificate[0]), + SslProtocols.Tls, + false); + } + } + catch (Exception) + { + try { stream.Close(); } + catch (Exception) { } + try { client.Close(); } + catch (Exception) { } + throw; + } + Init(client, stream); + } + + private void Init(TcpClient socket, Stream stream) + { + _socket = socket; + _stream = stream; + ObjectSerializerFactory fact = + ObjectSerializerFactory.GetInstance(); + _encoder = fact.NewBinarySerializer(_stream); + _decoder = fact.NewBinaryDeserializer(_stream); + } + + public void Dispose() + { + Flush(); + _stream.Close(); + _socket.Close(); + } + + public void Flush() + { + _encoder.Flush(); + } + + public void WriteObject(object obj) + { + _encoder.WriteObject(obj); + } + + public object ReadObject() + { + //flush first in case there is any data in the + //output buffer + Flush(); + return _decoder.ReadObject(); + } + } + #endregion + + #region RemoteConnectorInfoImpl + /// + /// internal class, public only for unit tests + /// + public sealed class RemoteConnectorInfoImpl : AbstractConnectorInfo + { + + + public RemoteConnectorInfoImpl() + { + + } + + //transient field, not serialized + public RemoteFrameworkConnectionInfo RemoteConnectionInfo { get; set; } + + } + #endregion + + #region RemoteConnectorInfoManagerImpl + public class RemoteConnectorInfoManagerImpl : ConnectorInfoManager + { + private IList _connectorInfo; + + private RemoteConnectorInfoManagerImpl() + { + + } + + public RemoteConnectorInfoManagerImpl(RemoteFrameworkConnectionInfo info) + { + using (RemoteFrameworkConnection connection = + new RemoteFrameworkConnection(info)) + { + connection.WriteObject(CultureInfo.CurrentUICulture); + connection.WriteObject(info.Key); + connection.WriteObject(new HelloRequest(HelloRequest.CONNECTOR_INFO)); + HelloResponse response = (HelloResponse)connection.ReadObject(); + if (response.Exception != null) + { + throw response.Exception; + } + IList remoteInfos = + response.ConnectorInfos; + //populate transient fields not serialized + foreach (RemoteConnectorInfoImpl remoteInfo in remoteInfos) + { + remoteInfo.RemoteConnectionInfo = info; + } + _connectorInfo = + CollectionUtil.NewReadOnlyList(remoteInfos); + } + + } + + /// + /// Derives another RemoteConnectorInfoManagerImpl with + /// a different RemoteFrameworkConnectionInfo but with the + /// same metadata + /// + /// + public RemoteConnectorInfoManagerImpl Derive(RemoteFrameworkConnectionInfo info) + { + RemoteConnectorInfoManagerImpl rv = new RemoteConnectorInfoManagerImpl(); + IList remoteInfosObj = + (IList)SerializerUtil.CloneObject(_connectorInfo); + IList remoteInfos = + CollectionUtil.NewList(remoteInfosObj); + foreach (ConnectorInfo remoteInfo in remoteInfos) + { + ((RemoteConnectorInfoImpl)remoteInfo).RemoteConnectionInfo = (info); + } + rv._connectorInfo = + CollectionUtil.AsReadOnlyList(remoteInfos); + return rv; + } + + public ConnectorInfo FindConnectorInfo(ConnectorKey key) + { + foreach (ConnectorInfo info in _connectorInfo) + { + if (info.ConnectorKey.Equals(key)) + { + return info; + } + } + return null; + } + + public IList ConnectorInfos + { + get + { + return _connectorInfo; + } + } + } + #endregion + + #region RemoteConnectorFacadeImpl + /// + /// Implements all the methods of the facade + /// + public class RemoteConnectorFacadeImpl : AbstractConnectorFacade + { + + internal readonly string remoteConnectorFacadeKey; + + /// + /// Builds up the maps of supported operations and calls. + /// + public RemoteConnectorFacadeImpl(APIConfigurationImpl configuration) + : base(GenerateRemoteConnectorFacadeKey(configuration), configuration.ConnectorInfo) + { + // Restore the original configuration settings + GetAPIConfiguration().ProducerBufferSize = configuration.ProducerBufferSize; + GetAPIConfiguration().TimeoutMap = configuration.TimeoutMap; + remoteConnectorFacadeKey = ConnectorFacadeKey; + } + + public RemoteConnectorFacadeImpl(RemoteConnectorInfoImpl connectorInfo, string configuration) + : base(configuration, connectorInfo) + { + remoteConnectorFacadeKey = GenerateRemoteConnectorFacadeKey(GetAPIConfiguration()); + } + + public RemoteConnectorFacadeImpl(RemoteConnectorInfoImpl connectorInfo, String config, + IConfigurationPropertyChangeListener changeListener) + : this(connectorInfo, config) + { + GetAPIConfiguration().ChangeListener = changeListener; + } + + private static string GenerateRemoteConnectorFacadeKey(APIConfigurationImpl configuration) + { + APIConfigurationImpl copy = new APIConfigurationImpl(configuration); + copy.ProducerBufferSize = 0; + copy.TimeoutMap = new Dictionary, int>(); + return SerializerUtil.SerializeBase64Object(copy); + } + + protected override APIOperation GetOperationImplementation(SafeType api) + { + if (api.RawType == typeof(IConnectorEventSubscriptionApiOp) || api.RawType == typeof(ISyncEventSubscriptionApiOp)) + { + //Not supported remotely with legacy communication protocol + return null; + } + // add remote proxy + InvocationHandler handler = new RemoteOperationInvocationHandler( + (RemoteConnectorInfoImpl)GetAPIConfiguration().ConnectorInfo, remoteConnectorFacadeKey, api); + APIOperation proxy = NewAPIOperationProxy(api, handler); + // now wrap the proxy in the appropriate timeout proxy + proxy = CreateTimeoutProxy(api, proxy); + // add logging proxy + proxy = CreateLoggingProxy(api, proxy); + + return proxy; + } + } + #endregion + + #region RemoteOperationInvocationHandler + /// + /// Invocation handler for all of our operations + /// + public class RemoteOperationInvocationHandler : InvocationHandler + { + private readonly RemoteConnectorInfoImpl _connectorInfo; + private readonly String _connectorFacadeKey; + private readonly SafeType _operation; + + public RemoteOperationInvocationHandler(RemoteConnectorInfoImpl connectorInfo, + String connectorFacadeKey, + SafeType operation) + { + _connectorInfo = connectorInfo; + _connectorFacadeKey = connectorFacadeKey; + _operation = operation; + } + + + public Object Invoke(Object proxy, MethodInfo method, Object[] args) + { + //don't proxy toString, hashCode, or equals + if (method.DeclaringType.Equals(typeof(object))) + { + return method.Invoke(this, args); + } + + //partition arguments into arguments that can + //be simply marshalled as part of the request and + //those that are response handlers + IList simpleMarshallArgs = + CollectionUtil.NewList(args); + ObjectStreamHandler streamHandlerArg = + ExtractStreamHandler(ReflectionUtil.GetParameterTypes(method), simpleMarshallArgs); + + //build the request object + RemoteFrameworkConnectionInfo connectionInfo = + _connectorInfo.RemoteConnectionInfo; + OperationRequest request = new OperationRequest( + _connectorInfo.ConnectorKey, _connectorFacadeKey, + _operation, + method.Name, + simpleMarshallArgs); + + //create the connection + RemoteFrameworkConnection connection = + new RemoteFrameworkConnection(connectionInfo); + try + { + connection.WriteObject(CultureInfo.CurrentUICulture); + connection.WriteObject(connectionInfo.Key); + //send the request + connection.WriteObject(request); + + //now process each response stream (if any) + if (streamHandlerArg != null) + { + HandleStreamResponse(connection, streamHandlerArg); + } + + //finally return the actual return value + OperationResponsePart response = + (OperationResponsePart)connection.ReadObject(); + if (response.Exception != null) + { + throw response.Exception; + } + return response.Result; + } + finally + { + connection.Dispose(); + } + } + /// + /// Handles a stream response until the end of the stream + /// + private static void HandleStreamResponse(RemoteFrameworkConnection connection, ObjectStreamHandler streamHandler) + { + Object response; + bool handleMore = true; + while (true) + { + response = connection.ReadObject(); + if (response is OperationResponsePart) + { + OperationResponsePart part = (OperationResponsePart)response; + if (part.Exception != null) + { + throw part.Exception; + } + object obj = + part.Result; + if (handleMore) + { + handleMore = streamHandler.Handle(obj); + } + } + else if (response is OperationResponsePause) + { + if (handleMore) + { + connection.WriteObject(new OperationRequestMoreData()); + } + else + { + connection.WriteObject(new OperationRequestStopData()); + } + } + else if (response is OperationResponseEnd) + { + break; + } + else + { + throw new ConnectorException("Unexpected response: " + response); + } + } + } + + /// + /// Partitions arguments into regular arguments and + /// stream arguments. + /// + /// The param types of the method + /// The passed-in arguments. As a + /// side-effect will be set to just the regular arguments. + /// The stream handler arguments. + private static ObjectStreamHandler ExtractStreamHandler(Type[] paramTypes, + IList arguments) + { + ObjectStreamHandler rv = null; + IList filteredArguments = new List(); + for (int i = 0; i < paramTypes.Length; i++) + { + Type paramType = paramTypes[i]; + object arg = arguments[i]; + if (StreamHandlerUtil.IsAdaptableToObjectStreamHandler(paramType)) + { + ObjectStreamHandler handler = StreamHandlerUtil.AdaptToObjectStreamHandler(paramType, arg); + if (rv != null) + { + throw new InvalidOperationException("Multiple stream handlers not supported"); + } + rv = handler; + } + else + { + filteredArguments.Add(arg); + } + } + arguments.Clear(); + CollectionUtil.AddAll(arguments, filteredArguments); + return rv; + } + } + #endregion + + #region RemoteWrappedException + /// + /// RemoteWrappedException wraps every exception which are received from Remote + /// Connector Server. + ///

+ /// This Exception is not allowed to use in Connectors!!! + ///

+ /// + /// + /// + /// This type of exception is not allowed to be serialise because this exception + /// represents any after deserialization. + /// + /// This code example show how to get the remote stack trace and how to use the + /// same catches to handle the exceptions regardless its origin. + /// + ///

+    /// 
+    ///  String stackTrace = null;
+    ///  try {
+    ///      try {
+    ///          facade.GetObject(ObjectClass.ACCOUNT, uid, null);
+    ///      } catch (RemoteWrappedException e) {
+    ///          stackTrace = e.StackTrace;
+    ///      }
+    ///  } catch (Throwable t) {
+    ///      
+    ///  }
+    /// 
+    /// 
+ ///
+ /// Since 1.4 + public sealed class RemoteWrappedException : ConnectorException + { + public const string FIELD_CLASS = "class"; + public const string FIELD_MESSAGE = "message"; + public const string FIELD_CAUSE = "cause"; + public const string FIELD_STACK_TRACE = "stackTrace"; + + private readonly string stackTrace; + + /// + ///
+        ///     
+        ///         {
+        ///              "class": "org.identityconnectors.framework.common.exceptions.ConnectorIOException",
+        ///              "message": "Sample Error Message",
+        ///              "cause": {
+        ///                  "class": "java.net.SocketTimeoutException",
+        ///                  "message": "Sample Error Message",
+        ///                  "cause": {
+        ///                      "class": "edu.example.CustomException",
+        ///                      "message": "Sample Error Message"
+        ///                  }
+        ///              },
+        ///              "stackTrace": "full stack trace for logging"
+        ///          }
+        ///     
+        /// 
+ ///
+ private IDictionary exception = null; + + /// + internal RemoteWrappedException(IDictionary exception) + : base((string)exception[FIELD_MESSAGE]) + { + this.exception = exception; + this.stackTrace = (string)exception[FIELD_STACK_TRACE]; + } + + public RemoteWrappedException(string throwableClass, string message, RemoteWrappedException cause, string stackTrace) + : base(message) + { + exception = new Dictionary(4); + exception[FIELD_CLASS] = Assertions.BlankChecked(throwableClass, "throwableClass"); + exception[FIELD_MESSAGE] = message; + if (null != cause) + { + exception[FIELD_CAUSE] = cause.exception; + } + if (null != stackTrace) + { + exception[FIELD_STACK_TRACE] = stackTrace; + } + this.stackTrace = stackTrace; + } + + /// + /// Gets the class name of the original exception. + /// + /// This value is constructed by {@code Exception.Type.FullName}. + /// + /// name of the original exception. + public string ExceptionClass + { + get + { + return (string)exception[FIELD_CLASS]; + } + } + + /// + /// Checks if the exception is the expected class. + /// + /// + /// the expected throwable class. + /// {@code true} if the class name are equals. + public bool Is(Type expected) + { + if (null == expected) + { + return false; + } + string className = ((string)exception[FIELD_CLASS]); + //The .NET Type.FullName property will not always yield results identical to the Java Class.getName method: + string classExpected = expected.FullName; + return classExpected.Equals(className, StringComparison.CurrentCultureIgnoreCase); + } + + /// + /// Returns the cause of original throwable or {@code null} if the cause is + /// nonexistent or unknown. (The cause is the throwable that caused the + /// original throwable to get thrown.) + /// + /// the cause of this throwable or {@code null} if the cause is + /// nonexistent or unknown. + public RemoteWrappedException Cause + { + get + { + object o = exception[FIELD_CAUSE]; + if (o is IDictionary) + { + return new RemoteWrappedException((IDictionary)o); + } + else + { + return null; + } + } + } + + public override string StackTrace + { + get + { + if (null == stackTrace) + { + return base.StackTrace; + } + else + { + return stackTrace; + } + } + } + + public string ReadStackTrace() + { + return stackTrace; + } + + /// + /// Wraps the Throwable into a RemoteWrappedException instance. + /// + /// + /// Exception to wrap or cast and return. + /// a RemoteWrappedException that either is the + /// specified exception or contains the specified exception. + public static RemoteWrappedException Wrap(Exception ex) + { + if (null == ex) + { + return null; + } + // don't bother to wrap a exception that is already a + // RemoteWrappedException. + if (ex is RemoteWrappedException) + { + return (RemoteWrappedException)ex; + } + return new RemoteWrappedException(convert(ex)); + } + + /// + /// Converts the throwable object to a new Map object that representing + /// itself. + /// + /// + /// the {@code Throwable} to be converted + /// the Map representing the throwable. + public static Dictionary convert(Exception throwable) + { + Dictionary exception = null; + if (null != throwable) + { + exception = new Dictionary(4); + //The .NET Type.FullName property will not always yield results identical to the Java Class.getName method: + exception[FIELD_CLASS] = throwable.GetType().FullName; + exception[FIELD_MESSAGE] = throwable.Message; + if (null != throwable.InnerException) + { + exception[FIELD_CAUSE] = buildCause(throwable.InnerException); + } + exception[FIELD_STACK_TRACE] = throwable.StackTrace; + } + return exception; + } + + private static IDictionary buildCause(Exception throwable) + { + IDictionary cause = new Dictionary(null != throwable.InnerException ? 3 : 2); + //The .NET Type.FullName property will not always yield results identical to the Java Class.getName method: + cause[FIELD_CLASS] = throwable.GetType().FullName; + cause[FIELD_MESSAGE] = throwable.Message; + if (null != throwable.InnerException) + { + cause[FIELD_CAUSE] = buildCause(throwable.InnerException); + } + return cause; + } + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/ApiRemoteMessages.cs b/dotnet/framework/FrameworkInternal/ApiRemoteMessages.cs new file mode 100644 index 00000000..445afac6 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/ApiRemoteMessages.cs @@ -0,0 +1,369 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +namespace Org.IdentityConnectors.Framework.Impl.Api.Remote.Messages +{ + #region HelloRequest + /// + /// internal class, public only for unit tests + /// + public class HelloRequest : Message + { + public const int SERVER_INFO = 4; + public const int CONNECTOR_KEY_LIST = 16; + // private const int DEFAULT_CONFIG = 32; + public const int CONNECTOR_INFO = CONNECTOR_KEY_LIST | SERVER_INFO; + + private readonly int _level; + + public HelloRequest(int infoLevel) + { + _level = infoLevel; + } + + public int GetInfoLevel() + { + return _level; + } + + private bool checkInfoLevel(int info) + { + return ((_level & info) == info); + } + + public bool isServerInfo() + { + return checkInfoLevel(SERVER_INFO); + } + + public bool isConnectorKeys() + { + return checkInfoLevel(CONNECTOR_KEY_LIST); + } + + public bool isConnectorInfo() + { + return checkInfoLevel(CONNECTOR_INFO); + } + } + #endregion + + #region HelloResponse + /// + /// internal class, public only for unit tests + /// + public class HelloResponse : Message + { + public const string SERVER_START_TIME = "SERVER_START_TIME"; + + /// + /// The exception + /// + private Exception _exception; + + private IDictionary _serverInfo; + + /// + /// List of connector infos, containing infos for all the connectors + /// on the server. + /// + private IList _connectorInfos; + + /// + /// List of connector keys, containing the keys of all the connectors + /// on the server. + /// + private IList _connectorKeys; + + public HelloResponse(Exception exception, + IDictionary serverInfo, + IList connectorKeys, + IList connectorInfos) + { + _exception = exception; + _serverInfo = CollectionUtil.AsReadOnlyDictionary(serverInfo); + _connectorKeys = CollectionUtil.NewReadOnlyList(connectorKeys); + _connectorInfos = CollectionUtil.NewReadOnlyList(connectorInfos); + } + + public Exception Exception + { + get + { + return _exception; + } + } + + public IList ConnectorInfos + { + get + { + return _connectorInfos; + } + } + + public IList ConnectorKeys + { + get + { + return _connectorKeys; + } + } + + public IDictionary ServerInfo + { + get + { + return _serverInfo; + } + } + + public DateTime? getStartTime() + { + object time = ServerInfo[SERVER_START_TIME]; + if (time is long) + { + return new DateTime((long)time); + } + return null; + } + } + #endregion + + #region Message + /// + /// internal class, public only for unit tests + /// + public interface Message + { + } + #endregion + + #region OperationRequest + /// + /// internal class, public only for unit tests + /// + public class OperationRequest : Message + { + /// + /// The key of the connector to operate on. + /// + private readonly ConnectorKey _connectorKey; + + /// + /// The configuration information to use. + /// + private readonly String _connectorFacadeKey; + + /// + /// The operation to perform. + /// + private readonly SafeType _operation; + + /// + /// The name of the method since operations can have more + /// than one method. + /// + /// + /// NOTE: this is case-insensitive + /// + private readonly String _operationMethodName; + + /// + /// The arguments to the operation. + /// + /// + /// In general, these correspond + /// to the actual arguments of the method. The one exception is + /// search - in this case, the callback is not passed. + /// + private readonly IList _arguments; + + public OperationRequest(ConnectorKey key, + String connectorFacadeKey, + SafeType operation, + string operationMethodName, + IList arguments) + { + _connectorKey = key; + _connectorFacadeKey = connectorFacadeKey; + _operation = operation; + _operationMethodName = operationMethodName; + _arguments = CollectionUtil.NewReadOnlyList(arguments); + } + + public ConnectorKey ConnectorKey + { + get + { + return _connectorKey; + } + } + + public String ConnectorFacadeKey + { + get + { + return _connectorFacadeKey; + } + } + + public SafeType Operation + { + get + { + return _operation; + } + } + + public string OperationMethodName + { + get + { + return _operationMethodName; + } + } + + public IList Arguments + { + get + { + return _arguments; + } + } + } + #endregion + + #region OperationRequestMoreData + /// + /// internal class, public only for unit tests + /// + public class OperationRequestMoreData : Message + { + public OperationRequestMoreData() + { + } + } + #endregion + + #region OperationRequestStopData + /// + /// internal class, public only for unit tests + /// + public class OperationRequestStopData : Message + { + public OperationRequestStopData() + { + } + } + #endregion + + #region OperationResponseEnd + /// + /// internal class, public only for unit tests + /// + public class OperationResponseEnd : Message + { + public OperationResponseEnd() + { + } + } + #endregion + + #region OperationResponsePart + /// + /// internal class, public only for unit tests + /// + public class OperationResponsePart : Message + { + private RemoteWrappedException _exception; + private Object _result; + + public OperationResponsePart(Exception ex, Object result) + { + _exception = RemoteWrappedException.Wrap(ex); + _result = result; + } + + public RemoteWrappedException Exception + { + get + { + return _exception; + } + } + + public Object Result + { + get + { + return _result; + } + } + } + #endregion + + #region OperationResponsePause + /// + /// internal class, public only for unit tests + /// + public class OperationResponsePause : Message + { + public OperationResponsePause() + { + } + } + #endregion + + #region EchoMessage + public class EchoMessage : Message + { + private object _object; + private string _objectXml; + public EchoMessage(object obj, string xml) + { + _object = obj; + _objectXml = xml; + } + public object Object + { + get + { + return _object; + } + } + public string ObjectXml + { + get + { + return _objectXml; + } + } + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/ExceptionUtil.cs b/dotnet/framework/FrameworkInternal/ExceptionUtil.cs new file mode 100644 index 00000000..353e36e9 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/ExceptionUtil.cs @@ -0,0 +1,83 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Reflection; +using Org.IdentityConnectors.Common; + +namespace Org.IdentityConnectors.Framework.Impl +{ + /// + /// Contains utilities to handle exceptions. + /// + public static class ExceptionUtil + { + private const string PreserveStackTraceMethodName = "InternalPreserveStackTrace"; + + /// + /// Preserves the stack trace of . + /// + /// The exception, the stack trace of which to be preserved. + /// In the .Net Framework the stack trace of an exception starts to get populated when it is thrown, + /// hence if an exception is re-thrown by a method upper in the call chain the stack trace will reflect that the + /// exception occurred at that position where the exception was actually re-thrown and the original stack trace will + /// be lost. + /// + /// + /// try + /// { + /// function_that_throws_an_exception_with_a_nested_one(); + /// } + /// catch( Exception ex ) + /// { + /// throw ex.InnerException; //clears the stack trace of the nested exception + /// } + /// + /// + /// There is no built-in support in .Net to preserve the stack trace, however, an internal method + /// of the class called + /// can be used to achieve this. Since it is an internal method it might be subject to change, therefore if it is + /// not possible to invoke the method by any reason the error cause will be traced, but it will not break the execution. + /// + public static void PreserveStackTrace(Exception exception) + { + Assertions.NullCheck(exception, "exception"); + + try + { + MethodInfo preserveStackTrace = typeof(Exception).GetMethod( + PreserveStackTraceMethodName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod); + + preserveStackTrace.Invoke(exception, null); + } + catch (Exception ex) + { + //it should not ever happen, but we have to make sure that if a next release of .Net Framework does not + //include the invoked method it will not break the execution + TraceUtil.TraceException(string.Format( + @"Could not set an exception to preserve its stack trace. Exception: ""{0}""", exception), ex); + return; + } + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/FrameworkInternal.csproj b/dotnet/framework/FrameworkInternal/FrameworkInternal.csproj new file mode 100644 index 00000000..f4b090f6 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/FrameworkInternal.csproj @@ -0,0 +1,123 @@ + + + + + {5B011775-B121-4EEE-A410-BA2D2F5BFB8B} + Debug + AnyCPU + Library + Org.IdentityConnectors + FrameworkInternal + Open Connectors Framework Internal + v4.5.2 + True + False + 4 + false + true + + + + prompt + 4 + true + bin\Debug\ + Full + False + True + DEBUG;TRACE + + + pdbonly + bin\Release\ + true + prompt + 4 + True + False + TRACE + + + False + Auto + 4194304 + AnyCPU + 4096 + + + false + + + false + + + + + + + 4.0 + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + {8B24461B-456A-4032-89A1-CD418F7B5B62} + Framework + + + {E6A207D2-E083-41BF-B522-D9D3EC09323E} + TestCommon + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/Resources.resx b/dotnet/framework/FrameworkInternal/Resources.resx new file mode 100755 index 00000000..92b2f09e --- /dev/null +++ b/dotnet/framework/FrameworkInternal/Resources.resx @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <?xml version='1.0' encoding='UTF-8'?> +<!--=======================================================--> +<!--= =--> +<!--= DTD for Connector Objects =--> +<!--= =--> +<!--=======================================================--> + +<!--=======================================================--> +<!--= =--> +<!--= All XML Objects =--> +<!--= =--> +<!--=======================================================--> + +<!ENTITY % exceptionTypes + "AlreadyExistsException | ConfigurationException | ConnectionBrokenException + | ConnectionFailedException | ConnectorIOException | InvalidPasswordException + | UnknownUidException | InvalidCredentialException | PermissionDeniedException + | ConnectorSecurityException | OperationTimeoutException | InvalidAttributeValueException + | PreconditionRequiredException | PreconditionFailedException | RetryableException + | RemoteWrappedException | ConnectorException + | RuntimeException | Exception | Throwable | PasswordExpiredException | IllegalArgumentException + "> + +<!ENTITY % messageTypes + "HelloRequest | HelloResponse | OperationRequest | OperationResponseEnd | + OperationResponsePart | OperationRequestMoreData | OperationRequestStopData | + OperationResponsePause | EchoMessage + "> + +<!ENTITY % filterTypes + "AndFilter | ContainsFilter | EndsWithFilter | EqualsFilter | ExtendedMatchFilter | + GreaterThanFilter | GreaterThanOrEqualFilter | LessThanFilter | + LessThanOrEqualFilter | NotFilter | OrFilter | PresenceFilter | StartsWithFilter | + ContainsAllValuesFilter + "> + +<!ENTITY % attributeTypes + "Attribute | Uid | Name"> + +<!ENTITY % primitiveTypes + "null | Array | Boolean | boolean | Character | char | Integer | + int | Long | long | Float | float | Double | double | String | + URI | File | BigDecimal | BigInteger | ByteArray | Class | + Map | List | Set | Locale | GuardedByteArray | GuardedString | + Byte | byte + "> + +<!ENTITY % xmlObject + "%primitiveTypes; | %exceptionTypes; | %messageTypes; | %filterTypes; | %attributeTypes; | +ObjectPoolConfiguration | ResultsHandlerConfiguration | ConfigurationProperty | ConfigurationProperties | +APIConfiguration | ConnectorMessages | ConnectorKey | ConnectorInfo | +UpdateApiOpType | AttributeInfo | ConnectorObject | ObjectClass | +ObjectClassInfo | Schema | Script | ScriptContext | OperationOptions | +OperationOptionInfo | SearchResult | SyncDeltaType | SyncToken | SyncDelta | QualifiedUid +"> + + + +<!--=======================================================--> +<!--= =--> +<!--= Top Level Element for object streams =--> +<!--= =--> +<!--=======================================================--> + + +<!ELEMENT MultiObject (( + %xmlObject; +)*)> + + +<!--=======================================================--> +<!--= =--> +<!--= Primitives =--> +<!--= =--> +<!--=======================================================--> + + + +<!ELEMENT null EMPTY> +<!ELEMENT Array ((%xmlObject;)*)> +<!ATTLIST Array + componentType CDATA #REQUIRED +> +<!ELEMENT Boolean (#PCDATA)> +<!ELEMENT boolean (#PCDATA)> +<!ELEMENT Character (#PCDATA)> +<!ELEMENT char (#PCDATA)> +<!ELEMENT Integer (#PCDATA)> +<!ELEMENT int (#PCDATA)> +<!ELEMENT Byte (#PCDATA)> +<!ELEMENT byte (#PCDATA)> +<!ELEMENT Long (#PCDATA)> +<!ELEMENT long (#PCDATA)> +<!ELEMENT Float (#PCDATA)> +<!ELEMENT float (#PCDATA)> +<!ELEMENT Double (#PCDATA)> +<!ELEMENT double (#PCDATA)> +<!ELEMENT String (#PCDATA)> +<!ELEMENT URI (#PCDATA)> +<!ELEMENT File (#PCDATA)> +<!ELEMENT BigDecimal EMPTY> +<!ATTLIST BigDecimal + unscaled CDATA #REQUIRED + scale CDATA #REQUIRED +> +<!ELEMENT BigInteger (#PCDATA)> +<!ELEMENT ByteArray (#PCDATA)> +<!ELEMENT Class (#PCDATA)> +<!ELEMENT Map ((MapEntry)*)> +<!ATTLIST Map + caseInsensitive CDATA #IMPLIED +> +<!ELEMENT MapEntry ((%xmlObject;),(%xmlObject;))> +<!ELEMENT Keys ((%xmlObject;)*)> +<!ELEMENT List ((%xmlObject;)*)> +<!ELEMENT Set ((%xmlObject;)*)> +<!ATTLIST Set + caseInsensitive CDATA #IMPLIED +> +<!ELEMENT Locale EMPTY> +<!ATTLIST Locale + language CDATA #IMPLIED + country CDATA #IMPLIED + variant CDATA #IMPLIED +> +<!ELEMENT GuardedByteArray (#PCDATA)> +<!ELEMENT GuardedString (#PCDATA)> + +<!--=======================================================--> +<!--= =--> +<!--= APIConfiguration =--> +<!--= =--> +<!--=======================================================--> + +<!ELEMENT ObjectPoolConfiguration EMPTY> +<!ATTLIST ObjectPoolConfiguration + maxObjects CDATA #IMPLIED + maxIdle CDATA #IMPLIED + maxWait CDATA #IMPLIED + minEvictableIdleTimeMillis CDATA #IMPLIED + minIdle CDATA #IMPLIED +> + +<!ELEMENT ResultsHandlerConfiguration EMPTY> +<!ATTLIST ResultsHandlerConfiguration + enableNormalizingResultsHandler CDATA #IMPLIED + enableFilteredResultsHandler CDATA #IMPLIED + enableCaseInsensitiveFilter CDATA #IMPLIED + enableAttributesToGetSearchResultsHandler CDATA #IMPLIED +> + +<!ELEMENT ConfigurationProperty (value,operations)> +<!ATTLIST ConfigurationProperty + order CDATA #IMPLIED + confidential CDATA #IMPLIED + required CDATA #IMPLIED + name CDATA #REQUIRED + helpMessageKey CDATA #REQUIRED + displayMessageKey CDATA #REQUIRED + groupMessageKey CDATA #REQUIRED + type CDATA #REQUIRED +> +<!ELEMENT value (%xmlObject;)> +<!ELEMENT operations (Class)*> +<!ELEMENT ConfigurationProperties ((ConfigurationProperty)*)> + +<!ELEMENT APIConfiguration (connectorPoolConfiguration,resultsHandlerConfiguration,ConfigurationProperties,timeoutMap,SupportedOperations)> +<!ATTLIST APIConfiguration + connectorPoolingSupported CDATA #REQUIRED + producerBufferSize CDATA #REQUIRED +> +<!ELEMENT connectorPoolConfiguration ((ObjectPoolConfiguration))> +<!ELEMENT resultsHandlerConfiguration ((ResultsHandlerConfiguration))> +<!ELEMENT timeoutMap (Map)> +<!ELEMENT SupportedOperations ((Class)*)> +<!ELEMENT ConnectorMessages (catalogs)> +<!ELEMENT catalogs (Map)> +<!ELEMENT ConnectorKey EMPTY> +<!ATTLIST ConnectorKey + bundleName CDATA #REQUIRED + bundleVersion CDATA #REQUIRED + connectorName CDATA #REQUIRED +> +<!ELEMENT ConnectorInfo (ConnectorKey,ConnectorMessages,APIConfiguration)> +<!ATTLIST ConnectorInfo + connectorDisplayNameKey CDATA #REQUIRED + connectorCategoryKey CDATA #REQUIRED +> + +<!--=======================================================--> +<!--= =--> +<!--= Common Objects =--> +<!--= =--> +<!--=======================================================--> +<!ELEMENT Attribute (Values)?> +<!ELEMENT Values ((%xmlObject;)*)> +<!ATTLIST Attribute + name CDATA #REQUIRED +> + +<!ELEMENT Uid (#PCDATA)> +<!ATTLIST Uid + uid CDATA #IMPLIED + revision CDATA #IMPLIED +> +<!ELEMENT Name (#PCDATA)> + + + +<!ELEMENT UpdateApiOpType (#PCDATA)> + + +<!ELEMENT AlreadyExistsException EMPTY> +<!ATTLIST AlreadyExistsException + message CDATA #IMPLIED +> +<!ELEMENT ConfigurationException EMPTY> +<!ATTLIST ConfigurationException + message CDATA #IMPLIED +> +<!ELEMENT ConnectionBrokenException EMPTY> +<!ATTLIST ConnectionBrokenException + message CDATA #IMPLIED +> +<!ELEMENT ConnectionFailedException EMPTY> +<!ATTLIST ConnectionFailedException + message CDATA #IMPLIED +> +<!ELEMENT ConnectorIOException EMPTY> +<!ATTLIST ConnectorIOException + message CDATA #IMPLIED +> +<!ELEMENT InvalidPasswordException EMPTY> +<!ATTLIST InvalidPasswordException + message CDATA #IMPLIED +> +<!ELEMENT PasswordExpiredException (Uid?)> +<!ATTLIST PasswordExpiredException + message CDATA #IMPLIED +> +<!ELEMENT UnknownUidException EMPTY> +<!ATTLIST UnknownUidException + message CDATA #IMPLIED +> +<!ELEMENT InvalidCredentialException EMPTY> +<!ATTLIST InvalidCredentialException + message CDATA #IMPLIED +> +<!ELEMENT PermissionDeniedException EMPTY> +<!ATTLIST PermissionDeniedException + message CDATA #IMPLIED +> +<!ELEMENT ConnectorSecurityException EMPTY> +<!ATTLIST ConnectorSecurityException + message CDATA #IMPLIED +> +<!ELEMENT OperationTimeoutException EMPTY> +<!ATTLIST OperationTimeoutException + message CDATA #IMPLIED +> +<!ELEMENT InvalidAttributeValueException EMPTY> +<!ATTLIST InvalidAttributeValueException + message CDATA #IMPLIED +> +<!ELEMENT PreconditionFailedException EMPTY> +<!ATTLIST PreconditionFailedException + message CDATA #IMPLIED +> +<!ELEMENT PreconditionRequiredException EMPTY> +<!ATTLIST PreconditionRequiredException + message CDATA #IMPLIED +> +<!ELEMENT RetryableException EMPTY> +<!ATTLIST RetryableException + message CDATA #IMPLIED +> +<!ELEMENT RemoteWrappedException (RemoteWrappedException?)> +<!ATTLIST RemoteWrappedException + class CDATA #IMPLIED + message CDATA #IMPLIED + stackTrace CDATA #IMPLIED +> +<!ELEMENT ConnectorException EMPTY> +<!ATTLIST ConnectorException + message CDATA #IMPLIED +> +<!ELEMENT IllegalArgumentException EMPTY> +<!ATTLIST IllegalArgumentException + message CDATA #IMPLIED +> +<!ELEMENT RuntimeException EMPTY> +<!ATTLIST RuntimeException + message CDATA #IMPLIED +> +<!ELEMENT Exception EMPTY> +<!ATTLIST Exception + message CDATA #IMPLIED +> +<!ELEMENT Throwable EMPTY> +<!ATTLIST Throwable + message CDATA #IMPLIED +> +<!ELEMENT AttributeInfo (AttributeInfoFlag*)> +<!ATTLIST AttributeInfo + name CDATA #REQUIRED + type CDATA #REQUIRED +> +<!ELEMENT AttributeInfoFlag EMPTY> +<!ATTLIST AttributeInfoFlag + value ( REQUIRED | MULTIVALUED | NOT_CREATABLE | NOT_UPDATEABLE | NOT_READABLE | NOT_RETURNED_BY_DEFAULT ) #REQUIRED +> +<!ELEMENT ConnectorObject (ObjectClass,Attributes)> +<!ELEMENT Attributes ((%attributeTypes;)*)> + +<!ELEMENT ObjectClass EMPTY> +<!ATTLIST ObjectClass + type CDATA #REQUIRED +> + +<!ELEMENT ObjectClassInfo (AttributeInfos)> +<!ATTLIST ObjectClassInfo + type CDATA #REQUIRED + container CDATA #IMPLIED +> +<!ELEMENT AttributeInfos ((AttributeInfo)*)> + +<!ELEMENT Schema (ObjectClassInfos,OperationOptionInfos,objectClassesByOperation,optionsByOperation)> +<!ELEMENT ObjectClassInfos ((ObjectClassInfo)*)> +<!ELEMENT OperationOptionInfos ((OperationOptionInfo)*)> +<!ELEMENT objectClassesByOperation (Map)> +<!ELEMENT optionsByOperation (Map)> + +<!ELEMENT scriptText (#PCDATA)> +<!ELEMENT scriptArguments (Map)> + +<!ELEMENT Script (scriptText)> +<!ATTLIST Script + scriptLanguage CDATA #REQUIRED +> + +<!ELEMENT ScriptContext (scriptArguments,scriptText)> +<!ATTLIST ScriptContext + scriptLanguage CDATA #REQUIRED +> + +<!ELEMENT OperationOptions (options)> +<!ELEMENT options (Map)> +<!ELEMENT OperationOptionInfo EMPTY> +<!ATTLIST OperationOptionInfo + name CDATA #REQUIRED + type CDATA #REQUIRED +> +<!ELEMENT SyncDeltaType EMPTY> +<!ATTLIST SyncDeltaType + value ( CREATE | UPDATE | CREATE_OR_UPDATE | DELETE ) #REQUIRED +> +<!ELEMENT SyncToken (value)> +<!ELEMENT SyncDelta (SyncDeltaType,SyncToken,PreviousUid?,ObjectClass?,Uid,ConnectorObject?)> + +<!ELEMENT PreviousUid (#PCDATA)> +<!ATTLIST PreviousUid + uid CDATA #IMPLIED + revision CDATA #IMPLIED +> + +<!ELEMENT QualifiedUid (ObjectClass,Uid)> + +<!ELEMENT CountPolicy EMPTY> +<!ATTLIST CountPolicy + value ( NONE | ESTIMATE | EXACT ) #REQUIRED +> +<!ELEMENT SearchResult (CountPolicy?)> +<!ATTLIST SearchResult + pagedResultsCookie CDATA #REQUIRED + remainingPagedResults CDATA #REQUIRED + totalPagedResults CDATA #REQUIRED +> + +<!--=======================================================--> +<!--= =--> +<!--= Filters =--> +<!--= =--> +<!--=======================================================--> + + +<!ELEMENT attribute (%attributeTypes;)> +<!ELEMENT AndFilter ((%filterTypes;),(%filterTypes;))> +<!ELEMENT ContainsFilter (attribute)> +<!ELEMENT EndsWithFilter (attribute)> +<!ELEMENT EqualsFilter (attribute)> +<!ELEMENT ExtendedMatchFilter (attribute)> +<!ATTLIST ExtendedMatchFilter + operator CDATA #IMPLIED +> +<!ELEMENT GreaterThanFilter (attribute)> +<!ELEMENT GreaterThanOrEqualFilter (attribute)> +<!ELEMENT LessThanFilter (attribute)> +<!ELEMENT LessThanOrEqualFilter (attribute)> +<!ELEMENT NotFilter (%filterTypes;)> +<!ELEMENT OrFilter ((%filterTypes;),(%filterTypes;))> +<!ELEMENT PresenceFilter (#PCDATA)> +<!ATTLIST PresenceFilter + name CDATA #IMPLIED +> +<!ELEMENT StartsWithFilter (attribute)> +<!ELEMENT ContainsAllValuesFilter (attribute)> + +<!--=======================================================--> +<!--= =--> +<!--= Messages =--> +<!--= =--> +<!--=======================================================--> +<!ELEMENT HelloRequest EMPTY> +<!ATTLIST HelloRequest + infoLevel CDATA #REQUIRED +> +<!ELEMENT serverInfoMap (Map)> +<!ELEMENT ConnectorKeys ((ConnectorKey)*)> +<!ELEMENT ConnectorInfos ((ConnectorInfo)*)> +<!ELEMENT exception (%exceptionTypes;)> +<!ELEMENT HelloResponse (exception,serverInfoMap,ConnectorInfos,ConnectorKeys)> +<!ELEMENT OperationRequest (ConnectorKey,Arguments)> +<!ATTLIST OperationRequest + operation CDATA #REQUIRED + operationMethodName CDATA #REQUIRED + connectorFacadeKey CDATA #REQUIRED +> +<!ELEMENT Arguments ((%xmlObject;)*)> +<!ELEMENT OperationResponseEnd EMPTY> +<!ELEMENT OperationResponsePart (exception,result)> +<!ELEMENT result ((%xmlObject;)*)> +<!ELEMENT OperationRequestMoreData EMPTY> +<!ELEMENT OperationRequestStopData EMPTY> +<!ELEMENT OperationResponsePause EMPTY> +<!ELEMENT EchoMessage (value,objectXml?)> +<!ELEMENT objectXml (#PCDATA)> + + + Test Framework Value + + + Account + + + Person + + + Group + + + Organization + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/Security.cs b/dotnet/framework/FrameworkInternal/Security.cs new file mode 100644 index 00000000..e16a75bc --- /dev/null +++ b/dotnet/framework/FrameworkInternal/Security.cs @@ -0,0 +1,136 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ + +using System.Security.Cryptography; + +namespace Org.IdentityConnectors.Common.Security.Impl +{ + public class EncryptorFactoryImpl : EncryptorFactory + { + private readonly Encryptor _defaultEncryptor; + + public EncryptorFactoryImpl() + { + _defaultEncryptor = new EncryptorImpl(); + } + + public override Encryptor GetDefaultEncryptor() + { + return _defaultEncryptor; + } + } + + public class EncryptorImpl : Encryptor + { + private readonly static byte[] _defaultKeyBytes = + { + (byte) 0x23,(byte) 0x65,(byte) 0x87,(byte) 0x22, + (byte) 0x59,(byte) 0x78,(byte) 0x54,(byte) 0x43, + (byte) 0x64,(byte) 0x05,(byte) 0x6A,(byte) 0xBD, + (byte) 0x34,(byte) 0xA2,(byte) 0x34,(byte) 0x57, + }; + private readonly static byte[] _defaultIvBytes = + { + (byte) 0x51,(byte) 0x65,(byte) 0x22,(byte) 0x23, + (byte) 0x64,(byte) 0x05,(byte) 0x6A,(byte) 0xBE, + (byte) 0x51,(byte) 0x65,(byte) 0x22,(byte) 0x23, + (byte) 0x64,(byte) 0x05,(byte) 0x6A,(byte) 0xBE, + }; + + public EncryptorImpl() + { + } + + public UnmanagedArray Decrypt(byte[] bytes) + { + using (SymmetricAlgorithm algo = Aes.Create()) + { + algo.Padding = PaddingMode.PKCS7; + algo.Mode = CipherMode.CBC; + algo.Key = _defaultKeyBytes; + algo.IV = _defaultIvBytes; + using (ICryptoTransform transform = algo.CreateDecryptor()) + { + return Decrypt2(bytes, transform); + } + } + } + + private unsafe UnmanagedArray Decrypt2(byte[] bytes, ICryptoTransform transform) + { + byte[] managedBytes = transform.TransformFinalBlock(bytes, 0, bytes.Length); + //pin it ASAP. this is a small race condition that is unavoidable due + //to the way the crypto API's return byte[]. + fixed (byte* dummy = managedBytes) + { + try + { + UnmanagedByteArray rv = new UnmanagedByteArray(managedBytes.Length); + for (int i = 0; i < rv.Length; i++) + { + rv[i] = managedBytes[i]; + } + return rv; + } + finally + { + SecurityUtil.Clear(managedBytes); + } + } + } + + public byte[] Encrypt(UnmanagedArray bytes) + { + using (SymmetricAlgorithm algo = Aes.Create()) + { + algo.Padding = PaddingMode.PKCS7; + algo.Mode = CipherMode.CBC; + algo.Key = _defaultKeyBytes; + algo.IV = _defaultIvBytes; + using (ICryptoTransform transform = algo.CreateEncryptor()) + { + return Encrypt2(bytes, transform); + } + } + } + + private unsafe byte[] Encrypt2(UnmanagedArray bytes, ICryptoTransform transform) + { + byte[] managedBytes = new byte[bytes.Length]; + fixed (byte* dummy = managedBytes) + { + try + { + SecurityUtil.UnmanagedBytesToManagedBytes(bytes, managedBytes); + byte[] rv = transform.TransformFinalBlock(managedBytes, 0, managedBytes.Length); + return rv; + } + finally + { + SecurityUtil.Clear(managedBytes); + } + } + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/Serializer.cs b/dotnet/framework/FrameworkInternal/Serializer.cs new file mode 100755 index 00000000..e09a9500 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/Serializer.cs @@ -0,0 +1,3169 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using System.IO; +using System.Collections.Generic; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Pooling; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Impl.Api; +using Org.IdentityConnectors.Framework.Impl.Api.Remote; +using Org.IdentityConnectors.Framework.Impl.Api.Remote.Messages; +using Org.IdentityConnectors.Framework.Impl.Serializer.Binary; +using Org.IdentityConnectors.Framework.Impl.Serializer.Xml; +using System.Globalization; +namespace Org.IdentityConnectors.Framework.Impl.Serializer +{ + #region Serialization Framework + internal abstract class AbstractObjectSerializationHandler + : ObjectTypeMapperImpl, ObjectSerializationHandler + { + + protected AbstractObjectSerializationHandler(Type handledClass, + String type) + : base(handledClass, type) + { + } + /// + /// Called to serialize the object. + /// + abstract public void Serialize(Object obj, ObjectEncoder encoder); + + /// + /// Called to deserialize the object. + /// + abstract public Object Deserialize(ObjectDecoder decoder); + } + + + internal class EnumSerializationHandler : + AbstractObjectSerializationHandler + { + public EnumSerializationHandler(Type clazz, String name) + : base(clazz, name) + { + } + + public override object Deserialize(ObjectDecoder decoder) + { + String val = decoder.ReadStringField("value", null); + Type enumClass = HandledObjectType; + Object rv = Enum.Parse(enumClass, val); + return rv; + } + + public override void Serialize(object obj, ObjectEncoder encoder) + { + Enum e = (Enum)obj; + encoder.WriteStringField("value", Enum.GetName(e.GetType(), e)); + } + } + + /// + /// Interface to abstract away the difference between deserializing + /// xml and binary + /// + internal interface ObjectDecoder + { + /// + /// Reads an object using the appropriate serializer for that object + /// + /// A hint of the field name. Ignored for binary + /// serialization. The subelement name for xml serialization + Object ReadObjectField(String fieldName, + Type expectedType, + Object dflt); + + /// + /// Reads a bool. + /// + /// A hint of the field name. Ignored for binary + /// serialization. The subelement name for xml serialization + bool ReadBooleanField(String fieldName, bool dflt); + + /// + /// Reads an int. + /// + /// A hint of the field name. Ignored for binary + /// serialization. The subelement name for xml serialization + int ReadIntField(String fieldName, int dflt); + + /// + /// Reads a long. + /// + /// A hint of the field name. Ignored for binary + /// serialization. The subelement name for xml serialization + long ReadLongField(String fieldName, long dflt); + + /// + /// Reads a float. + /// + /// A hint of the field name. Ignored for binary + /// serialization. The subelement name for xml serialization + float ReadFloatField(String fieldName, float dflt); + + /// + /// Reads a double. + /// + /// A hint of the field name. Ignored for binary + /// serialization. The subelement name for xml serialization + /// The value to serialize + double ReadDoubleField(String fieldName, double dflt); + + /// + /// Reads a double. + /// + /// A hint of the field name. Ignored for binary + /// serialization. The subelement name for xml serialization + /// The value to serialize + string ReadStringField(String fieldName, string dflt); + + /// + /// Reads a double. + /// + /// A hint of the field name. Ignored for binary + /// serialization. The subelement name for xml serialization + /// The value to serialize + Type ReadClassField(String fieldName, Type dflt); + + /// + /// Reads the value in-line. + /// + String ReadStringContents(); + + /// + /// Reads the value in-line. + /// + bool ReadBooleanContents(); + + /// + /// Reads the value in-line. + /// + int ReadIntContents(); + + /// + /// reads the value in-line. + /// + long ReadLongContents(); + + /// + /// Reads the value in-line. + /// + float ReadFloatContents(); + + /// + /// reads the value in-line. + /// + double ReadDoubleContents(); + + /// + /// reads the value in-line. + /// + byte ReadByteContents(); + + /// + /// reads the value in-line. + /// + byte[] ReadByteArrayContents(); + + /// + /// reads the value in-line. + /// + Type ReadClassContents(); + + /// + /// Returns the number of anonymous sub-objects. + /// + int GetNumSubObjects(); + + /// + /// Reads a sub-object + /// + Object ReadObjectContents(int index); + } + + /// + /// Interface to abstract away the difference between serializing + /// xml and binary + /// + internal interface ObjectEncoder + { + /// + /// Writes an object using the appropriate serializer for that object + /// + /// A hint of the field name. Ignored for binary + /// serialization. Becomes the subelement name for xml serialization + /// The object to serialize + void WriteObjectField(String fieldName, Object obj, bool inline); + + /// + /// Writes a boolean. + /// + /// A hint of the field name. Ignored for binary + /// serialization. Becomes the subelement name for xml serialization + /// The value to serialize + void WriteBooleanField(String fieldName, bool v); + + /// + /// Writes an int. + /// + /// A hint of the field name. Ignored for binary + /// serialization. Becomes the subelement name for xml serialization + /// The value to serialize + void WriteIntField(String fieldName, int v); + + /// + /// Writes a long. + /// + /// A hint of the field name. Ignored for binary + /// serialization. Becomes the subelement name for xml serialization + /// The value to serialize + void WriteLongField(String fieldName, long v); + + /// + /// Writes a float. + /// + /// A hint of the field name. Ignored for binary + /// serialization. Becomes the subelement name for xml serialization + /// The value to serialize + void WriteFloatField(String fieldName, float v); + + /// + /// Writes a double. + /// + /// A hint of the field name. Ignored for binary + /// serialization. Becomes the subelement name for xml serialization + /// The value to serialize + void WriteDoubleField(String fieldName, double v); + + /// + /// Writes a double. + /// + /// A hint of the field name. Ignored for binary + /// serialization. Becomes the subelement name for xml serialization + /// The value to serialize + void WriteStringField(String fieldName, string v); + + /// + /// Writes a double. + /// + /// A hint of the field name. Ignored for binary + /// serialization. Becomes the subelement name for xml serialization + /// The value to serialize + void WriteClassField(String fieldName, Type v); + + /// + /// Writes the value in-line. + /// + void WriteStringContents(String str); + + /// + /// Writes the value in-line. + /// + void WriteBooleanContents(bool v); + + /// + /// Writes the value in-line. + /// + void WriteIntContents(int v); + + /// + /// Writes the value in-line. + /// + void WriteLongContents(long v); + + /// + /// Writes the value in-line. + /// + void WriteFloatContents(float v); + + /// + /// Writes the value in-line. + /// + void WriteDoubleContents(double v); + + /// + /// Writes the value in-line. + /// + void WriteByteContents(byte v); + + /// + /// Special case for byte [] that uses base64 encoding for XML + /// + void WriteByteArrayContents(byte[] v); + + /// + /// Writes the value in-line. + /// + void WriteClassContents(Type v); + + /// + /// Writes a sub-object + /// + void WriteObjectContents(object o); + } + + /// + /// Interface to be implemented to handle the serialization/ + /// deserialization of an object. + /// + internal interface ObjectSerializationHandler : ObjectTypeMapper + { + /// + /// Called to serialize the object. + /// + void Serialize(Object obj, ObjectEncoder encoder); + + /// + /// Called to deserialize the object. + /// + Object Deserialize(ObjectDecoder decoder); + } + + internal static class ObjectSerializerRegistry + { + private static readonly IList HANDLERS = + new List(); + + private static readonly IDictionary + HANDLERS_BY_SERIAL_TYPE = new Dictionary(); + + static ObjectSerializerRegistry() + { + CollectionUtil.AddAll(HANDLERS, Primitives.HANDLERS); + CollectionUtil.AddAll(HANDLERS, OperationMappings.MAPPINGS); + CollectionUtil.AddAll(HANDLERS, APIConfigurationHandlers.HANDLERS); + CollectionUtil.AddAll(HANDLERS, FilterHandlers.HANDLERS); + CollectionUtil.AddAll(HANDLERS, CommonObjectHandlers.HANDLERS); + CollectionUtil.AddAll(HANDLERS, MessageHandlers.HANDLERS); + //object is special - just map the type, but don't actually + //serialize + HANDLERS.Add(new ObjectTypeMapperImpl(typeof(object), "Object")); + + foreach (ObjectTypeMapper handler in HANDLERS) + { + if (HANDLERS_BY_SERIAL_TYPE.ContainsKey(handler.HandledSerialType)) + { + throw new Exception("More than one handler of the" + + " same type: " + handler.HandledSerialType); + } + HANDLERS_BY_SERIAL_TYPE[handler.HandledSerialType] = + handler; + } + } + + /// + /// Mapping by class. + /// + /// + /// Dynamically built since actual class may be + /// a subclass. + /// + private static readonly IDictionary + HANDLERS_BY_OBJECT_TYPE = + new Dictionary(); + + public static ObjectTypeMapper GetMapperBySerialType(String type) + { + return CollectionUtil.GetValue(HANDLERS_BY_SERIAL_TYPE, type, null); + } + + public static ObjectTypeMapper GetMapperByObjectType(Type clazz) + { + lock (HANDLERS_BY_OBJECT_TYPE) + { + ObjectTypeMapper rv = + CollectionUtil.GetValue(HANDLERS_BY_OBJECT_TYPE, clazz, null); + if (rv == null) + { + foreach (ObjectTypeMapper handler in HANDLERS) + { + IDelegatingObjectTypeMapper delegator = + handler as IDelegatingObjectTypeMapper; + ObjectTypeMapper effectiveHandler; + if (delegator != null) + { + effectiveHandler = delegator.FindMapperDelegate(clazz); + } + else + { + effectiveHandler = handler; + } + if (effectiveHandler != null) + { + Type handledClass = + effectiveHandler.HandledObjectType; + if (effectiveHandler.MatchSubclasses) + { + if (handledClass.IsAssignableFrom(clazz)) + { + rv = effectiveHandler; + break; + } + } + else if (handledClass.Equals(clazz)) + { + rv = effectiveHandler; + break; + } + } + } + HANDLERS_BY_OBJECT_TYPE[clazz] = rv; + } + return rv; + } + } + public static ObjectSerializationHandler GetHandlerBySerialType(String type) + { + ObjectTypeMapper rv = GetMapperBySerialType(type); + return rv as ObjectSerializationHandler; + } + + public static ObjectSerializationHandler GetHandlerByObjectType(Type clazz) + { + ObjectTypeMapper rv = GetMapperByObjectType(clazz); + return rv as ObjectSerializationHandler; + } + } + + /// + /// ObjectTypeMappers can implement this interface as well. If + /// they do, they can handle specific generic types as well. + /// + internal interface IDelegatingObjectTypeMapper + { + ObjectTypeMapper FindMapperDelegate(Type type); + } + + /// + /// Interface to be implemented to handle the serialization/ + /// deserialization of an object. + /// + internal interface ObjectTypeMapper + { + /// + /// Returns the type of object being serialized. + /// + /// + /// This is + /// an abstract type name that is intended to be language + /// neutral. + /// + String HandledSerialType { get; } + + /// + /// Returns the java class handled by this handler. + /// + Type HandledObjectType { get; } + + /// + /// Should we match subclasses of the given class or only + /// the exact class? + /// + bool MatchSubclasses { get; } + } + + internal class ObjectTypeMapperImpl : ObjectTypeMapper + { + private Type _handledClass; + private String _handledType; + + public ObjectTypeMapperImpl(Type handledClass, String handledType) + { + _handledClass = handledClass; + _handledType = handledType; + } + + public Type HandledObjectType + { + get + { + return _handledClass; + } + } + + public String HandledSerialType + { + get + { + return _handledType; + } + } + + public virtual bool MatchSubclasses + { + get + { + return false; + } + } + } + + internal abstract class AbstractExceptionHandler : AbstractObjectSerializationHandler + where T : Exception + { + protected AbstractExceptionHandler(String typeName) + : base(typeof(T), typeName) + { + } + + public override Object Deserialize(ObjectDecoder decoder) + { + String message = decoder.ReadStringField("message", null); + return CreateException(message); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + Exception val = (Exception)obj; + encoder.WriteStringField("message", val.Message); + } + public override bool MatchSubclasses + { + get { return true; } + } + protected abstract T CreateException(String message); + } + + + #endregion + + #region Primitives + + internal static class Primitives + { + public static readonly IList HANDLERS = + new List(); + static Primitives() + { + HANDLERS.Add(new BooleanHandler(typeof(bool?), "Boolean")); + HANDLERS.Add(new BooleanHandler(typeof(bool), "boolean")); + HANDLERS.Add(new ByteHandler(typeof(byte?), "Byte")); + HANDLERS.Add(new ByteHandler(typeof(byte), "byte")); + HANDLERS.Add(new CharacterHandler(typeof(char?), "Character")); + HANDLERS.Add(new CharacterHandler(typeof(char), "char")); + HANDLERS.Add(new IntegerHandler(typeof(int?), "Integer")); + HANDLERS.Add(new IntegerHandler(typeof(int), "int")); + HANDLERS.Add(new LongHandler(typeof(long?), "Long")); + HANDLERS.Add(new LongHandler(typeof(long), "long")); + HANDLERS.Add(new FloatHandler(typeof(float?), "Float")); + HANDLERS.Add(new FloatHandler(typeof(float), "float")); + HANDLERS.Add(new DoubleHandler(typeof(double?), "Double")); + HANDLERS.Add(new DoubleHandler(typeof(double), "double")); + HANDLERS.Add(new StringHandler()); + HANDLERS.Add(new URIHandler()); + HANDLERS.Add(new FileHandler()); + HANDLERS.Add(new BigIntegerHandler()); + HANDLERS.Add(new BigDecimalHandler()); + HANDLERS.Add(new ByteArrayHandler()); + HANDLERS.Add(new ClassHandler()); + HANDLERS.Add(new MapEntryHandler()); + HANDLERS.Add(new MapHandler()); + HANDLERS.Add(new ListHandler()); + HANDLERS.Add(new SetHandler()); + HANDLERS.Add(new LocaleHandler()); + HANDLERS.Add(new GuardedByteArrayHandler()); + HANDLERS.Add(new GuardedStringHandler()); + } + private class BooleanHandler : AbstractObjectSerializationHandler + { + public BooleanHandler(Type objectType, String serialType) + : base(objectType, serialType) + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + bool val = decoder.ReadBooleanContents(); + return val; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + bool val = (bool)obj; + encoder.WriteBooleanContents(val); + } + } + private class CharacterHandler : AbstractObjectSerializationHandler + { + public CharacterHandler(Type objectType, String serialType) + : base(objectType, serialType) + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + String val = decoder.ReadStringContents(); + return val.ToCharArray()[0]; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + char val = (char)obj; + encoder.WriteStringContents(val.ToString()); + } + } + private class IntegerHandler : AbstractObjectSerializationHandler + { + public IntegerHandler(Type objectType, String serialType) + : base(objectType, serialType) + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + int val = decoder.ReadIntContents(); + return val; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + int val = (int)obj; + encoder.WriteIntContents(val); + } + } + private class LongHandler : AbstractObjectSerializationHandler + { + public LongHandler(Type objectType, String serialType) + : base(objectType, serialType) + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + long val = decoder.ReadLongContents(); + return val; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + long val = (long)obj; + encoder.WriteLongContents(val); + } + } + private class FloatHandler : AbstractObjectSerializationHandler + { + public FloatHandler(Type objectType, String serialType) + : base(objectType, serialType) + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + float val = decoder.ReadFloatContents(); + return val; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + float val = (float)obj; + encoder.WriteFloatContents(val); + } + } + private class DoubleHandler : AbstractObjectSerializationHandler + { + public DoubleHandler(Type objectType, String serialType) + : base(objectType, serialType) + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + double val = decoder.ReadDoubleContents(); + return val; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + double val = (double)obj; + encoder.WriteDoubleContents(val); + } + } + private class StringHandler : AbstractObjectSerializationHandler + { + public StringHandler() + : base(typeof(string), "String") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + string val = decoder.ReadStringContents(); + return val; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + string val = (string)obj; + encoder.WriteStringContents(val); + } + } + private class URIHandler : AbstractObjectSerializationHandler + { + public URIHandler() + : base(typeof(Uri), "URI") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + string val = decoder.ReadStringContents(); + return new Uri(val); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + Uri val = (Uri)obj; + encoder.WriteStringContents(val.ToString()); + } + } + private class FileHandler : AbstractObjectSerializationHandler + { + public FileHandler() + : base(typeof(FileName), "File") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + string val = decoder.ReadStringContents(); + return new FileName(val); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + FileName val = (FileName)obj; + encoder.WriteStringContents(val.Path); + } + } + private class BigDecimalHandler : AbstractObjectSerializationHandler + { + public BigDecimalHandler() + : base(typeof(BigDecimal), "BigDecimal") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + BigInteger unscaled = + new BigInteger(decoder.ReadStringField("unscaled", null)); + int scale = decoder.ReadIntField("scale", 0); + return new BigDecimal(unscaled, scale); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + BigDecimal val = (BigDecimal)obj; + encoder.WriteStringField("unscaled", val.UnscaledValue.Value); + encoder.WriteIntField("scale", val.Scale); + } + } + private class BigIntegerHandler : AbstractObjectSerializationHandler + { + public BigIntegerHandler() + : base(typeof(BigInteger), "BigInteger") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + string val = decoder.ReadStringContents(); + return new BigInteger(val); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + BigInteger val = (BigInteger)obj; + encoder.WriteStringContents(val.Value); + } + } + private class ByteHandler : AbstractObjectSerializationHandler + { + public ByteHandler(Type objectType, String serialType) + : base(objectType, serialType) + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + return decoder.ReadByteContents(); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + byte val = (byte)obj; + encoder.WriteByteContents(val); + } + } + private class ByteArrayHandler : AbstractObjectSerializationHandler + { + public ByteArrayHandler() + : base(typeof(byte[]), "ByteArray") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + return decoder.ReadByteArrayContents(); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + byte[] val = (byte[])obj; + encoder.WriteByteArrayContents(val); + } + } + private class ClassHandler : AbstractObjectSerializationHandler + { + public ClassHandler() + : base(typeof(Type), "Class") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + return decoder.ReadClassContents(); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + Type val = (Type)obj; + encoder.WriteClassContents(val); + } + + /// + /// In C#, the actual Type of Type is RuntimeType + /// + public override bool MatchSubclasses + { + get { return true; } + } + } + + private class MapEntry + { + internal object key; + internal object val; + public MapEntry(object key, object val) + { + this.key = key; + this.val = val; + } + } + private class MapEntryHandler : + AbstractObjectSerializationHandler + { + + public MapEntryHandler() + : base(typeof(MapEntry), "MapEntry") + { + } + public override Object Deserialize(ObjectDecoder decoder) + { + Object key = decoder.ReadObjectContents(0); + Object val = decoder.ReadObjectContents(1); + return new MapEntry(key, val); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + MapEntry entry = (MapEntry)obj; + encoder.WriteObjectContents(entry.key); + encoder.WriteObjectContents(entry.val); + } + } + private class MapHandler : + AbstractObjectSerializationHandler, + IDelegatingObjectTypeMapper + { + public MapHandler() + : base(typeof(IDictionary), "Map") + { + } + public ObjectTypeMapper FindMapperDelegate(Type type) + { + //get the IDictionary interface that this type implements + Type interfaceType = ReflectionUtil.FindInHierarchyOf + (typeof(IDictionary<,>), type); + if (interfaceType != null) + { + Type[] keyAndValue = interfaceType.GetGenericArguments(); + if (keyAndValue.Length != 2) + { + throw new Exception("Cannot serialize type: " + type); + } + Type mapHandlerRawType = typeof(MapHandler<,>); + Type mapHandlerType = + mapHandlerRawType.MakeGenericType(keyAndValue); + return (ObjectTypeMapper)Activator.CreateInstance(mapHandlerType); + } + return null; + } + public override Object Deserialize(ObjectDecoder decoder) + { + bool caseInsensitive = + decoder.ReadBooleanField("caseInsensitive", false); + if (caseInsensitive) + { + IDictionary rv = + CollectionUtil.NewCaseInsensitiveDictionary(); + int count = decoder.GetNumSubObjects(); + for (int i = 0; i < count; i++) + { + MapEntry entry = (MapEntry)decoder.ReadObjectContents(i); + rv["" + entry.key] = entry.val; + } + return rv; + } + else + { + IDictionary rv = + new Dictionary(); + int count = decoder.GetNumSubObjects(); + for (int i = 0; i < count; i++) + { + MapEntry entry = (MapEntry)decoder.ReadObjectContents(i); + rv[entry.key] = entry.val; + } + return rv; + } + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + IDictionary map = (IDictionary)obj; + if (CollectionUtil.IsCaseInsensitiveDictionary(map)) + { + encoder.WriteBooleanField("caseInsensitive", true); + } + else if (map is SortedDictionary) + { + throw new Exception("Serialization of SortedDictionary not supported"); + } + foreach (KeyValuePair entry in map) + { + MapEntry myEntry = new MapEntry(entry.Key, entry.Value); + encoder.WriteObjectContents(myEntry); + } + } + + public override bool MatchSubclasses + { + get { return true; } + } + } + private class ListHandler : + AbstractObjectSerializationHandler, + IDelegatingObjectTypeMapper + { + + public ListHandler() + : base(typeof(IList), "List") + { + } + public ObjectTypeMapper FindMapperDelegate(Type type) + { + //in C#, arrays implement IList + if (type.IsArray) + { + return null; + } + //get the IList interface that this type implements + Type interfaceType = ReflectionUtil.FindInHierarchyOf + (typeof(IList<>), type); + if (interfaceType != null) + { + Type[] val = interfaceType.GetGenericArguments(); + if (val.Length != 1) + { + throw new Exception("Cannot serialize type: " + type); + } + Type listHandlerRawType = typeof(ListHandler<>); + Type listHandlerType = + listHandlerRawType.MakeGenericType(val); + return (ObjectTypeMapper)Activator.CreateInstance(listHandlerType); + } + return null; + } + public override Object Deserialize(ObjectDecoder decoder) + { + IList rv = + new List(); + int count = decoder.GetNumSubObjects(); + for (int i = 0; i < count; i++) + { + rv.Add(decoder.ReadObjectContents(i)); + } + return rv; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + IList list = (IList)obj; + foreach (T o in list) + { + encoder.WriteObjectContents(o); + } + } + + public override bool MatchSubclasses + { + get { return true; } + } + } + private class SetHandler : + AbstractObjectSerializationHandler, + IDelegatingObjectTypeMapper + { + + public SetHandler() + : base(typeof(ICollection), "Set") + { + } + public ObjectTypeMapper FindMapperDelegate(Type type) + { + //in C#, arrays implement IList + if (type.IsArray) + { + return null; + } + + //get the IList interface that this type implements + Type interfaceType = ReflectionUtil.FindInHierarchyOf + (typeof(ICollection<>), type); + if (interfaceType != null) + { + Type[] val = interfaceType.GetGenericArguments(); + if (val.Length != 1) + { + throw new Exception("Cannot serialize type: " + type); + } + Type setHandlerRawType = typeof(SetHandler<>); + Type setHandlerType = + setHandlerRawType.MakeGenericType(val); + return (ObjectTypeMapper)Activator.CreateInstance(setHandlerType); + } + return null; + } + public override Object Deserialize(ObjectDecoder decoder) + { + bool caseInsensitive = + decoder.ReadBooleanField("caseInsensitive", false); + if (caseInsensitive) + { + ICollection rv = + CollectionUtil.NewCaseInsensitiveSet(); + int count = decoder.GetNumSubObjects(); + for (int i = 0; i < count; i++) + { + rv.Add("" + decoder.ReadObjectContents(i)); + } + return rv; + } + else + { + ICollection rv = + new HashSet(); + int count = decoder.GetNumSubObjects(); + for (int i = 0; i < count; i++) + { + rv.Add(decoder.ReadObjectContents(i)); + } + return rv; + } + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ICollection list = (ICollection)obj; + if (CollectionUtil.IsCaseInsensitiveSet(list)) + { + encoder.WriteBooleanField("caseInsensitive", true); + } + foreach (T o in list) + { + encoder.WriteObjectContents(o); + } + } + + public override bool MatchSubclasses + { + get { return true; } + } + } + private class LocaleHandler : AbstractObjectSerializationHandler + { + public LocaleHandler() + : base(typeof(CultureInfo), "Locale") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + string language = decoder.ReadStringField("language", ""); + string country = decoder.ReadStringField("country", ""); + string variant = decoder.ReadStringField("variant", ""); + Locale locale = new Locale(language, country, variant); + return locale.ToCultureInfo(); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + CultureInfo cultureInfo = (CultureInfo)obj; + Locale locale = Locale.FindLocale(cultureInfo); + encoder.WriteStringField("language", locale.Language); + encoder.WriteStringField("country", locale.Country); + encoder.WriteStringField("variant", locale.Variant); + + } + } + + private class GuardedByteArrayHandler : AbstractObjectSerializationHandler + { + public GuardedByteArrayHandler() + : base(typeof(GuardedByteArray), "GuardedByteArray") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + byte[] encryptedBytes = null; + UnmanagedArray clearBytes = null; + try + { + encryptedBytes = decoder.ReadByteArrayContents(); + clearBytes = EncryptorFactory.GetInstance().GetDefaultEncryptor().Decrypt(encryptedBytes); + GuardedByteArray rv = new GuardedByteArray(); + for (int i = 0; i < clearBytes.Length; i++) + { + rv.AppendByte(clearBytes[i]); + } + return rv; + } + finally + { + if (clearBytes != null) + { + clearBytes.Dispose(); + } + SecurityUtil.Clear(encryptedBytes); + } + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + GuardedByteArray str = (GuardedByteArray)obj; + str.Access(new GuardedByteArray.LambdaAccessor( + clearBytes => + { + byte[] encryptedBytes = null; + try + { + encryptedBytes = EncryptorFactory.GetInstance().GetDefaultEncryptor().Encrypt(clearBytes); + encoder.WriteByteArrayContents(encryptedBytes); + } + finally + { + SecurityUtil.Clear(encryptedBytes); + } + })); + } + } + + private class GuardedStringHandler : AbstractObjectSerializationHandler + { + public GuardedStringHandler() + : base(typeof(GuardedString), "GuardedString") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + byte[] encryptedBytes = null; + UnmanagedArray clearBytes = null; + UnmanagedArray clearChars = null; + try + { + encryptedBytes = decoder.ReadByteArrayContents(); + clearBytes = EncryptorFactory.GetInstance().GetDefaultEncryptor().Decrypt(encryptedBytes); + clearChars = SecurityUtil.BytesToChars(clearBytes); + GuardedString rv = new GuardedString(); + for (int i = 0; i < clearChars.Length; i++) + { + rv.AppendChar(clearChars[i]); + } + return rv; + } + finally + { + if (clearBytes != null) + { + clearBytes.Dispose(); + } + if (clearChars != null) + { + clearChars.Dispose(); + } + SecurityUtil.Clear(encryptedBytes); + } + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + GuardedString str = (GuardedString)obj; + str.Access(new GuardedString.LambdaAccessor( + clearChars => + { + UnmanagedArray clearBytes = null; + byte[] encryptedBytes = null; + try + { + clearBytes = SecurityUtil.CharsToBytes(clearChars); + encryptedBytes = EncryptorFactory.GetInstance().GetDefaultEncryptor().Encrypt(clearBytes); + encoder.WriteByteArrayContents(encryptedBytes); + } + finally + { + if (clearBytes != null) + { + clearBytes.Dispose(); + } + SecurityUtil.Clear(encryptedBytes); + } + })); + } + } + } + #endregion + + #region APIConfigurationHandlers + internal static class APIConfigurationHandlers + { + public static readonly IList HANDLERS = + new List(); + static APIConfigurationHandlers() + { + HANDLERS.Add(new ConnectionPoolingConfigurationHandler()); + HANDLERS.Add(new ResultsHandlerConfigurationHandler()); + HANDLERS.Add(new ConfigurationPropertyHandler()); + HANDLERS.Add(new ConfigurationPropertiesHandler()); + HANDLERS.Add(new APIConfigurationHandler()); + HANDLERS.Add(new ConnectorMessagesHandler()); + HANDLERS.Add(new ConnectorKeyHandler()); + HANDLERS.Add(new ConnectorInfoHandler()); + } + private class ConnectionPoolingConfigurationHandler : AbstractObjectSerializationHandler + { + public ConnectionPoolingConfigurationHandler() + : base(typeof(ObjectPoolConfiguration), "ObjectPoolConfiguration") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + ObjectPoolConfiguration rv = + new ObjectPoolConfiguration(); + rv.MaxObjects = (decoder.ReadIntField("maxObjects", rv.MaxObjects)); + rv.MaxIdle = (decoder.ReadIntField("maxIdle", rv.MaxIdle)); + rv.MaxWait = (decoder.ReadLongField("maxWait", rv.MaxWait)); + rv.MinEvictableIdleTimeMillis = ( + decoder.ReadLongField("minEvictableIdleTimeMillis", rv.MinEvictableIdleTimeMillis)); + rv.MinIdle = ( + decoder.ReadIntField("minIdle", rv.MinIdle)); + return rv; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ObjectPoolConfiguration val = + (ObjectPoolConfiguration)obj; + encoder.WriteIntField("maxObjects", + val.MaxObjects); + encoder.WriteIntField("maxIdle", + val.MaxIdle); + encoder.WriteLongField("maxWait", + val.MaxWait); + encoder.WriteLongField("minEvictableIdleTimeMillis", + val.MinEvictableIdleTimeMillis); + encoder.WriteIntField("minIdle", + val.MinIdle); + } + } + private class ResultsHandlerConfigurationHandler : AbstractObjectSerializationHandler + { + public ResultsHandlerConfigurationHandler() + : base(typeof(ResultsHandlerConfiguration), "ResultsHandlerConfiguration") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + ResultsHandlerConfiguration rv = + new ResultsHandlerConfiguration(); + rv.EnableNormalizingResultsHandler = (decoder.ReadBooleanField("enableNormalizingResultsHandler", rv.EnableNormalizingResultsHandler)); + rv.EnableFilteredResultsHandler = (decoder.ReadBooleanField("enableFilteredResultsHandler", rv.EnableFilteredResultsHandler)); + rv.EnableCaseInsensitiveFilter = (decoder.ReadBooleanField("enableCaseInsensitiveFilter", rv.EnableCaseInsensitiveFilter)); + rv.EnableAttributesToGetSearchResultsHandler = ( + decoder.ReadBooleanField("enableAttributesToGetSearchResultsHandler", rv.EnableAttributesToGetSearchResultsHandler)); + return rv; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ResultsHandlerConfiguration val = + (ResultsHandlerConfiguration)obj; + encoder.WriteBooleanField("enableNormalizingResultsHandler", + val.EnableNormalizingResultsHandler); + encoder.WriteBooleanField("enableFilteredResultsHandler", + val.EnableFilteredResultsHandler); + encoder.WriteBooleanField("enableCaseInsensitiveFilter", + val.EnableCaseInsensitiveFilter); + encoder.WriteBooleanField("enableAttributesToGetSearchResultsHandler", + val.EnableAttributesToGetSearchResultsHandler); + } + } + private class ConfigurationPropertyHandler : AbstractObjectSerializationHandler + { + public ConfigurationPropertyHandler() + : base(typeof(ConfigurationPropertyImpl), "ConfigurationProperty") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + ConfigurationPropertyImpl rv = new ConfigurationPropertyImpl(); + rv.Order = (decoder.ReadIntField("order", 0)); + rv.IsConfidential = (decoder.ReadBooleanField("confidential", false)); + rv.IsRequired = decoder.ReadBooleanField("required", false); + rv.Name = (decoder.ReadStringField("name", null)); + rv.HelpMessageKey = ( + decoder.ReadStringField("helpMessageKey", null)); + rv.DisplayMessageKey = ( + decoder.ReadStringField("displayMessageKey", null)); + rv.GroupMessageKey = ( + decoder.ReadStringField("groupMessageKey", null)); + rv.ValueType = ( + decoder.ReadClassField("type", null)); + rv.Value = ( + decoder.ReadObjectField("value", null, null)); + ICollection operationsObj = + (ICollection)decoder.ReadObjectField("operations", typeof(ICollection), null); + ICollection> operations = + new HashSet>(); + foreach (object o in operationsObj) + { + Type type = (Type)o; + operations.Add(SafeType.ForRawType(type)); + } + rv.Operations = operations; + + return rv; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ConfigurationPropertyImpl val = + (ConfigurationPropertyImpl)obj; + encoder.WriteIntField("order", + val.Order); + encoder.WriteBooleanField("confidential", + val.IsConfidential); + encoder.WriteBooleanField("required", + val.IsRequired); + encoder.WriteStringField("name", + val.Name); + encoder.WriteStringField("helpMessageKey", + val.HelpMessageKey); + encoder.WriteStringField("displayMessageKey", + val.DisplayMessageKey); + encoder.WriteStringField("groupMessageKey", + val.GroupMessageKey); + encoder.WriteClassField("type", + val.ValueType); + encoder.WriteObjectField("value", + val.Value, + false); + ICollection operationsObj = + new HashSet(); + foreach (SafeType op in val.Operations) + { + operationsObj.Add(op.RawType); + } + encoder.WriteObjectField("operations", operationsObj, true); + } + } + private class ConfigurationPropertiesHandler : AbstractObjectSerializationHandler + { + public ConfigurationPropertiesHandler() + : base(typeof(ConfigurationPropertiesImpl), "ConfigurationProperties") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + ConfigurationPropertiesImpl rv = + new ConfigurationPropertiesImpl(); + IList props = + new List + (); + int count = decoder.GetNumSubObjects(); + for (int i = 0; i < count; i++) + { + ConfigurationPropertyImpl prop = + (ConfigurationPropertyImpl)decoder.ReadObjectContents(i); + props.Add(prop); + } + rv.Properties = props; + return rv; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ConfigurationPropertiesImpl val = + (ConfigurationPropertiesImpl)obj; + IList props = + val.Properties; + foreach (ConfigurationPropertyImpl prop in props) + { + encoder.WriteObjectContents(prop); + } + } + } + private class APIConfigurationHandler : AbstractObjectSerializationHandler + { + public APIConfigurationHandler() + : base(typeof(APIConfigurationImpl), "APIConfiguration") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + APIConfigurationImpl rv = new APIConfigurationImpl(); + rv.IsConnectorPoolingSupported = ( + decoder.ReadBooleanField("connectorPoolingSupported", false)); + rv.ConnectorPoolConfiguration = ( + (ObjectPoolConfiguration) + decoder.ReadObjectField("connectorPoolConfiguration", null, null)); + rv.ResultsHandlerConfiguration = ( + (ResultsHandlerConfiguration) + decoder.ReadObjectField("resultsHandlerConfiguration", null, null)); + rv.ConfigurationProperties = ((ConfigurationPropertiesImpl) + decoder.ReadObjectField("ConfigurationProperties", typeof(ConfigurationPropertiesImpl), null)); + IDictionary timeoutMapObj = + (IDictionary)decoder.ReadObjectField("timeoutMap", null, null); + IDictionary, int> timeoutMap = + new Dictionary, int>(); + foreach (KeyValuePair entry in timeoutMapObj) + { + Type type = (Type)entry.Key; + int val = (int)entry.Value; + timeoutMap[SafeType.ForRawType(type)] = val; + } + rv.TimeoutMap = timeoutMap; + ICollection supportedOperationsObj = + (ICollection)decoder.ReadObjectField("SupportedOperations", typeof(ICollection), null); + ICollection> supportedOperations = + new HashSet>(); + foreach (object obj in supportedOperationsObj) + { + Type type = (Type)obj; + supportedOperations.Add(SafeType.ForRawType(type)); + } + rv.SupportedOperations = supportedOperations; + rv.ProducerBufferSize = (decoder.ReadIntField("producerBufferSize", 0)); + return rv; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + APIConfigurationImpl val = + (APIConfigurationImpl)obj; + + ICollection supportedOperations = + new HashSet(); + if (val.SupportedOperations != null) + { + foreach (SafeType op in val.SupportedOperations) + { + supportedOperations.Add(op.RawType); + } + } + IDictionary timeoutMap = + new Dictionary(); + if (val.TimeoutMap != null) + { + foreach (KeyValuePair, int> entry in val.TimeoutMap) + { + timeoutMap[entry.Key.RawType] = entry.Value; + } + } + + encoder.WriteIntField("producerBufferSize", + val.ProducerBufferSize); + encoder.WriteBooleanField("connectorPoolingSupported", + val.IsConnectorPoolingSupported); + encoder.WriteObjectField("connectorPoolConfiguration", + val.ConnectorPoolConfiguration, false); + encoder.WriteObjectField("resultsHandlerConfiguration", + val.ResultsHandlerConfiguration, false); + encoder.WriteObjectField("ConfigurationProperties", + val.ConfigurationProperties, true); + encoder.WriteObjectField("timeoutMap", + timeoutMap, false); + encoder.WriteObjectField("SupportedOperations", + supportedOperations, true); + } + } + private class ConnectorMessagesHandler : AbstractObjectSerializationHandler + { + public ConnectorMessagesHandler() + : base(typeof(ConnectorMessagesImpl), "ConnectorMessages") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + ConnectorMessagesImpl rv = new ConnectorMessagesImpl(); + IDictionary catalogsObj = + (IDictionary)decoder.ReadObjectField("catalogs", null, null); + IDictionary> + catalogs = new Dictionary>(); + foreach (KeyValuePair entry in catalogsObj) + { + CultureInfo key = (CultureInfo)entry.Key; + IDictionary valObj = (IDictionary)entry.Value; + IDictionary val = + CollectionUtil.NewDictionary(valObj); + catalogs[key] = val; + } + rv.Catalogs = (catalogs); + return rv; + + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ConnectorMessagesImpl val = + (ConnectorMessagesImpl)obj; + encoder.WriteObjectField("catalogs", + val.Catalogs, false); + } + } + + private class ConnectorKeyHandler : AbstractObjectSerializationHandler + { + public ConnectorKeyHandler() + : base(typeof(ConnectorKey), "ConnectorKey") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + String bundleName = + decoder.ReadStringField("bundleName", null); + String bundleVersion = + decoder.ReadStringField("bundleVersion", null); + String connectorName = + decoder.ReadStringField("connectorName", null); + return new ConnectorKey(bundleName, bundleVersion, connectorName); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ConnectorKey val = (ConnectorKey)obj; + encoder.WriteStringField("bundleName", + val.BundleName); + encoder.WriteStringField("bundleVersion", + val.BundleVersion); + encoder.WriteStringField("connectorName", + val.ConnectorName); + } + } + + private class ConnectorInfoHandler : AbstractObjectSerializationHandler + { + public ConnectorInfoHandler() + : base(typeof(RemoteConnectorInfoImpl), "ConnectorInfo") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + RemoteConnectorInfoImpl rv = new RemoteConnectorInfoImpl(); + rv.ConnectorDisplayNameKey = ( + decoder.ReadStringField("connectorDisplayNameKey", null)); + rv.ConnectorCategoryKey = ( + decoder.ReadStringField("connectorCategoryKey", null)); + rv.ConnectorKey = ((ConnectorKey) + decoder.ReadObjectField("ConnectorKey", typeof(ConnectorKey), null)); + rv.Messages = ((ConnectorMessagesImpl) + decoder.ReadObjectField("ConnectorMessages", typeof(ConnectorMessagesImpl), null)); + rv.DefaultAPIConfiguration = ((APIConfigurationImpl) + decoder.ReadObjectField("APIConfiguration", typeof(APIConfigurationImpl), null)); + return rv; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + RemoteConnectorInfoImpl val = + (RemoteConnectorInfoImpl)obj; + encoder.WriteStringField("connectorDisplayNameKey", + val.ConnectorDisplayNameKey); + encoder.WriteStringField("connectorCategoryKey", + val.ConnectorCategoryKey); + encoder.WriteObjectField("ConnectorKey", + val.ConnectorKey, true); + encoder.WriteObjectField("ConnectorMessages", + val.Messages, true); + encoder.WriteObjectField("APIConfiguration", + val.DefaultAPIConfiguration, true); + } + } + + } + #endregion + + #region ObjectSerializerFactoryImpl + public class ObjectSerializerFactoryImpl : ObjectSerializerFactory + { + public override BinaryObjectDeserializer NewBinaryDeserializer(Stream i) + { + return new BinaryObjectDecoder(i); + } + + public override BinaryObjectSerializer NewBinarySerializer(Stream os) + { + return new BinaryObjectEncoder(os); + } + public override XmlObjectSerializer NewXmlSerializer(TextWriter w, + bool includeHeader, + bool multiObject) + { + return new XmlObjectSerializerImpl(w, includeHeader, multiObject); + } + + public override void DeserializeXmlStream(TextReader reader, + XmlObjectResultsHandler handler, + bool validate) + { + XmlObjectParser.parse(reader, handler, validate); + } + + } + #endregion + + #region OperationMappings + internal static class OperationMappings + { + public static readonly IList MAPPINGS = + new List(); + + static OperationMappings() + { + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(AuthenticationApiOp), + "AuthenticationApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(ResolveUsernameApiOp), + "ResolveUsernameApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(SearchApiOp), + "SearchApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(ValidateApiOp), + "ValidateApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(CreateApiOp), + "CreateApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(SchemaApiOp), + "SchemaApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(UpdateApiOp), + "UpdateApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(DeleteApiOp), + "DeleteApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(GetApiOp), + "GetApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(TestApiOp), + "TestApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(ScriptOnConnectorApiOp), + "ScriptOnConnectorApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(ScriptOnResourceApiOp), + "ScriptOnResourceApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(SyncApiOp), + "SyncApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(IConnectorEventSubscriptionApiOp), + "ConnectorEventSubscriptionApiOp")); + MAPPINGS.Add(new ObjectTypeMapperImpl(typeof(ISyncEventSubscriptionApiOp), + "SyncEventSubscriptionApiOp")); + } + } + #endregion + + #region CommonObjectHandlers + internal static class CommonObjectHandlers + { + public static readonly IList HANDLERS = + new List(); + static CommonObjectHandlers() + { + HANDLERS.Add(new AlreadyExistsExceptionHandler()); + HANDLERS.Add(new ConfigurationExceptionHandler()); + HANDLERS.Add(new ConnectionBrokenExceptionHandler()); + HANDLERS.Add(new ConnectionFailedExceptionHandler()); + HANDLERS.Add(new ConnectorIOExceptionHandler()); + HANDLERS.Add(new InvalidAttributeValueExceptionHandler()); + HANDLERS.Add(new PasswordExpiredExceptionHandler()); + HANDLERS.Add(new InvalidPasswordExceptionHandler()); + HANDLERS.Add(new UnknownUidExceptionHandler()); + HANDLERS.Add(new InvalidCredentialExceptionHandler()); + HANDLERS.Add(new PermissionDeniedExceptionHandler()); + HANDLERS.Add(new PreconditionFailedExceptionHandler()); + HANDLERS.Add(new PreconditionRequiredExceptionHandler()); + HANDLERS.Add(new RemoteWrappedExceptionHandler()); + HANDLERS.Add(new RetryableExceptionHandler()); + HANDLERS.Add(new ConnectorSecurityExceptionHandler()); + HANDLERS.Add(new OperationTimeoutExceptionHandler()); + HANDLERS.Add(new ConnectorExceptionHandler()); + HANDLERS.Add(new ArgumentExceptionHandler()); + HANDLERS.Add(new RuntimeExceptionHandler()); + HANDLERS.Add(new ExceptionHandler()); + HANDLERS.Add(new ThrowableHandler()); + HANDLERS.Add(new AttributeHandler()); + HANDLERS.Add(new AttributeInfoHandler()); + HANDLERS.Add(new ConnectorObjectHandler()); + HANDLERS.Add(new NameHandler()); + HANDLERS.Add(new ObjectClassHandler()); + HANDLERS.Add(new EnumSerializationHandler(typeof(SearchResult.CountPolicy), + "CountPolicy")); + HANDLERS.Add(new SearchResultHandler()); + HANDLERS.Add(new SortKeyHandler()); + HANDLERS.Add(new ObjectClassInfoHandler()); + HANDLERS.Add(new SchemaHandler()); + HANDLERS.Add(new UidHandler()); + HANDLERS.Add(new ScriptHandler()); + HANDLERS.Add(new ScriptContextHandler()); + HANDLERS.Add(new OperationOptionsHandler()); + HANDLERS.Add(new OperationOptionInfoHandler()); + HANDLERS.Add(new EnumSerializationHandler(typeof(ConnectorAttributeInfo.Flags), + "AttributeInfoFlag")); + HANDLERS.Add(new EnumSerializationHandler(typeof(SyncDeltaType), + "SyncDeltaType")); + HANDLERS.Add(new SyncTokenHandler()); + HANDLERS.Add(new SyncDeltaHandler()); + HANDLERS.Add(new QualifiedUidHandler()); + } + + private class AlreadyExistsExceptionHandler : AbstractExceptionHandler + { + public AlreadyExistsExceptionHandler() + : base("AlreadyExistsException") + { + + } + protected override AlreadyExistsException CreateException(String msg) + { + return new AlreadyExistsException(msg); + } + } + private class ConfigurationExceptionHandler : AbstractExceptionHandler + { + public ConfigurationExceptionHandler() + : base("ConfigurationException") + { + + } + protected override ConfigurationException CreateException(String msg) + { + return new ConfigurationException(msg); + } + } + private class ConnectionBrokenExceptionHandler : AbstractExceptionHandler + { + public ConnectionBrokenExceptionHandler() + : base("ConnectionBrokenException") + { + + } + protected override ConnectionBrokenException CreateException(String msg) + { + return new ConnectionBrokenException(msg); + } + } + private class ConnectionFailedExceptionHandler : AbstractExceptionHandler + { + public ConnectionFailedExceptionHandler() + : base("ConnectionFailedException") + { + + } + protected override ConnectionFailedException CreateException(String msg) + { + return new ConnectionFailedException(msg); + } + } + private class ConnectorIOExceptionHandler : AbstractExceptionHandler + { + public ConnectorIOExceptionHandler() + : base("ConnectorIOException") + { + + } + protected override ConnectorIOException CreateException(String msg) + { + return new ConnectorIOException(msg); + } + } + private class InvalidAttributeValueExceptionHandler : AbstractExceptionHandler + { + public InvalidAttributeValueExceptionHandler() + : base("InvalidAttributeValueException") + { + + } + protected override InvalidAttributeValueException CreateException(String msg) + { + return new InvalidAttributeValueException(msg); + } + } + private class PasswordExpiredExceptionHandler : AbstractExceptionHandler + { + public PasswordExpiredExceptionHandler() + : base("PasswordExpiredException") + { + + } + + public override Object Deserialize(ObjectDecoder decoder) + { + Uid uid = (Uid)decoder.ReadObjectField("Uid", typeof(Uid), null); + PasswordExpiredException ex = + (PasswordExpiredException)base.Deserialize(decoder); + ex.Uid = uid; + return ex; + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + base.Serialize(obj, encoder); + PasswordExpiredException val = (PasswordExpiredException)obj; + encoder.WriteObjectField("Uid", val.Uid, true); + } + protected override PasswordExpiredException CreateException(String msg) + { + return new PasswordExpiredException(msg); + } + } + private class InvalidPasswordExceptionHandler : AbstractExceptionHandler + { + public InvalidPasswordExceptionHandler() + : base("InvalidPasswordException") + { + + } + protected override InvalidPasswordException CreateException(String msg) + { + return new InvalidPasswordException(msg); + } + } + private class UnknownUidExceptionHandler : AbstractExceptionHandler + { + public UnknownUidExceptionHandler() + : base("UnknownUidException") + { + + } + protected override UnknownUidException CreateException(String msg) + { + return new UnknownUidException(msg); + } + } + private class InvalidCredentialExceptionHandler : AbstractExceptionHandler + { + public InvalidCredentialExceptionHandler() + : base("InvalidCredentialException") + { + + } + protected override InvalidCredentialException CreateException(String msg) + { + return new InvalidCredentialException(msg); + } + } + private class PermissionDeniedExceptionHandler : AbstractExceptionHandler + { + public PermissionDeniedExceptionHandler() + : base("PermissionDeniedException") + { + + } + protected override PermissionDeniedException CreateException(String msg) + { + return new PermissionDeniedException(msg); + } + } + private class ConnectorSecurityExceptionHandler : AbstractExceptionHandler + { + public ConnectorSecurityExceptionHandler() + : base("ConnectorSecurityException") + { + + } + protected override ConnectorSecurityException CreateException(String msg) + { + return new ConnectorSecurityException(msg); + } + } + private class OperationTimeoutExceptionHandler : AbstractExceptionHandler + { + public OperationTimeoutExceptionHandler() + : base("OperationTimeoutException") + { + + } + protected override OperationTimeoutException CreateException(String msg) + { + return new OperationTimeoutException(msg); + } + } + private class ConnectorExceptionHandler : AbstractExceptionHandler + { + public ConnectorExceptionHandler() + : base("ConnectorException") + { + + } + protected override ConnectorException CreateException(String msg) + { + return new ConnectorException(msg); + } + } + private class PreconditionFailedExceptionHandler : AbstractExceptionHandler + { + public PreconditionFailedExceptionHandler() + : base("PreconditionFailedException") + { + + } + protected override PreconditionFailedException CreateException(String msg) + { + return new PreconditionFailedException(msg); + } + } + private class PreconditionRequiredExceptionHandler : AbstractExceptionHandler + { + public PreconditionRequiredExceptionHandler() + : base("PreconditionRequiredException") + { + + } + protected override PreconditionRequiredException CreateException(String msg) + { + return new PreconditionRequiredException(msg); + } + } + private class RemoteWrappedExceptionHandler : AbstractObjectSerializationHandler + { + public RemoteWrappedExceptionHandler() + : base(typeof(RemoteWrappedException), "RemoteWrappedException") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + //The .NET Type.FullName property will not always yield results identical to the Java Class.getName method: + string throwableClass = decoder.ReadStringField(RemoteWrappedException.FIELD_CLASS, typeof(ConnectorException).FullName); + string message = decoder.ReadStringField(RemoteWrappedException.FIELD_MESSAGE, null); + RemoteWrappedException cause = (RemoteWrappedException)decoder.ReadObjectField("RemoteWrappedException", typeof(RemoteWrappedException), null); + string stackTrace = decoder.ReadStringField(RemoteWrappedException.FIELD_STACK_TRACE, null); + + return new RemoteWrappedException(throwableClass, message, cause, stackTrace); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + RemoteWrappedException val = (RemoteWrappedException)obj; + encoder.WriteStringField(RemoteWrappedException.FIELD_CLASS, val.ExceptionClass); + encoder.WriteStringField(RemoteWrappedException.FIELD_MESSAGE, val.Message); + encoder.WriteObjectField("RemoteWrappedException", val.InnerException, true); + encoder.WriteStringField(RemoteWrappedException.FIELD_STACK_TRACE, val.ReadStackTrace()); + } + } + private class RetryableExceptionHandler : AbstractExceptionHandler + { + public RetryableExceptionHandler() + : base("RetryableException") + { + + } + protected override RetryableException CreateException(String msg) + { + return RetryableException.Wrap(msg, (Exception)null); + } + } + + //RuntimeException, Exception, and Throwable + //when going from Java to C#, these always become + //Exception. + //when going from C# to Java, these always become + //RuntimeException + private class RuntimeExceptionHandler : AbstractExceptionHandler + { + public RuntimeExceptionHandler() + : base("RuntimeException") + { + + } + protected override Exception CreateException(String msg) + { + return new Exception(msg); + } + } + + private class ArgumentExceptionHandler : AbstractExceptionHandler + { + public ArgumentExceptionHandler() + : base("IllegalArgumentException") + { + + } + protected override ArgumentException CreateException(string msg) + { + return new ArgumentException(msg); + } + } + + private class ExceptionHandler : AbstractExceptionHandler + { + public ExceptionHandler() + : base("Exception") + { + + } + protected override Exception CreateException(String msg) + { + return new Exception(msg); + } + } + private class ThrowableHandler : AbstractExceptionHandler + { + public ThrowableHandler() + : base("Throwable") + { + + } + protected override Exception CreateException(String msg) + { + return new Exception(msg); + } + } + + private abstract class AbstractAttributeHandler + : AbstractObjectSerializationHandler + where T : ConnectorAttribute + { + + protected AbstractAttributeHandler(String typeName) + : base(typeof(T), typeName) + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + String name = decoder.ReadStringField("name", null); + IList val = (IList)decoder.ReadObjectField("Values", typeof(IList), null); + return CreateAttribute(name, val); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + ConnectorAttribute val = (ConnectorAttribute)obj; + encoder.WriteStringField("name", val.Name); + encoder.WriteObjectField("Values", val.Value, true); + } + + protected abstract T CreateAttribute(String name, IList val); + } + + private class AttributeHandler + : AbstractAttributeHandler + { + public AttributeHandler() + : base("Attribute") + { + + } + protected override ConnectorAttribute CreateAttribute(String name, IList value) + { + return ConnectorAttributeBuilder.Build(name, value); + } + } + + private class AttributeInfoHandler : AbstractObjectSerializationHandler + { + public AttributeInfoHandler() + : base(typeof(ConnectorAttributeInfo), "AttributeInfo") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + ConnectorAttributeInfoBuilder builder = new ConnectorAttributeInfoBuilder(); + builder.Name = ( + decoder.ReadStringField("name", null)); + builder.ValueType = ( + decoder.ReadClassField("type", null)); + ConnectorAttributeInfo.Flags flags = ConnectorAttributeInfo.Flags.NONE; + int count = decoder.GetNumSubObjects(); + for (int i = 0; i < count; i++) + { + object o = decoder.ReadObjectContents(i); + if (o is ConnectorAttributeInfo.Flags) + { + ConnectorAttributeInfo.Flags f = + (ConnectorAttributeInfo.Flags)o; + flags |= f; + } + } + builder.InfoFlags = flags; + return builder.Build(); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ConnectorAttributeInfo val = (ConnectorAttributeInfo)obj; + encoder.WriteStringField("name", val.Name); + encoder.WriteClassField("type", val.ValueType); + ConnectorAttributeInfo.Flags flags = val.InfoFlags; + foreach (Enum e in Enum.GetValues(typeof(ConnectorAttributeInfo.Flags))) + { + ConnectorAttributeInfo.Flags flag = + (ConnectorAttributeInfo.Flags)e; + if ((flags & flag) != 0) + { + encoder.WriteObjectContents(flag); + } + } + } + } + + private class ConnectorObjectHandler : AbstractObjectSerializationHandler + { + public ConnectorObjectHandler() + : base(typeof(ConnectorObject), "ConnectorObject") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + ObjectClass oclass = + (ObjectClass)decoder.ReadObjectField("ObjectClass", typeof(ObjectClass), null); + ICollection attsObj = + (ICollection)decoder.ReadObjectField("Attributes", typeof(ICollection), null); + ICollection atts = + CollectionUtil.NewSet(attsObj); + return new ConnectorObject(oclass, atts); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ConnectorObject val = (ConnectorObject)obj; + encoder.WriteObjectField("ObjectClass", val.ObjectClass, true); + encoder.WriteObjectField("Attributes", val.GetAttributes(), true); + } + } + private class NameHandler + : AbstractObjectSerializationHandler + { + public NameHandler() + : base(typeof(Name), "Name") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + String str = decoder.ReadStringContents(); + return new Name(str); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + Name val = (Name)obj; + encoder.WriteStringContents(val.GetNameValue()); + } + } + private class ObjectClassHandler : AbstractObjectSerializationHandler + { + public ObjectClassHandler() + : base(typeof(ObjectClass), "ObjectClass") + { + + } + public override object Deserialize(ObjectDecoder decoder) + { + string type = decoder.ReadStringField("type", null); + return new ObjectClass(type); + } + + public override void Serialize(object obj, ObjectEncoder encoder) + { + ObjectClass val = (ObjectClass)obj; + encoder.WriteStringField("type", val.GetObjectClassValue()); + } + } + + private class ObjectClassInfoHandler : AbstractObjectSerializationHandler + { + public ObjectClassInfoHandler() + : base(typeof(ObjectClassInfo), "ObjectClassInfo") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + string type = + decoder.ReadStringField("type", null); + bool container = + decoder.ReadBooleanField("container", false); + + ICollection attrInfoObj = + (ICollection)decoder.ReadObjectField("AttributeInfos", typeof(ICollection), null); + + ICollection attrInfo = + CollectionUtil.NewSet(attrInfoObj); + + return new ObjectClassInfo(type, attrInfo, container); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ObjectClassInfo val = (ObjectClassInfo)obj; + encoder.WriteStringField("type", val.ObjectType); + encoder.WriteBooleanField("container", val.IsContainer); + encoder.WriteObjectField("AttributeInfos", val.ConnectorAttributeInfos, true); + } + } + private class SchemaHandler : AbstractObjectSerializationHandler + { + public SchemaHandler() + : base(typeof(Schema), "Schema") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + + ICollection objectClassesObj = + (ICollection)decoder.ReadObjectField("ObjectClassInfos", typeof(ICollection), null); + ICollection objectClasses = + CollectionUtil.NewSet(objectClassesObj); + IDictionary objectClassesByName = new Dictionary(); + foreach (ObjectClassInfo info in objectClasses) + { + objectClassesByName[info.ObjectType] = info; + } + ICollection operationOptionsObj = + (ICollection)decoder.ReadObjectField("OperationOptionInfos", typeof(ICollection), null); + ICollection operationOptions = + CollectionUtil.NewSet(operationOptionsObj); + IDictionary optionsByName = new Dictionary(); + foreach (OperationOptionInfo info in operationOptions) + { + optionsByName[info.Name] = info; + } + IDictionary objectClassNamesByOperationObj = + (IDictionary)decoder.ReadObjectField("objectClassesByOperation", null, null); + IDictionary, ICollection> + objectClassesByOperation = + new Dictionary, ICollection>(); + foreach (KeyValuePair entry in objectClassNamesByOperationObj) + { + SafeType op = SafeType.ForRawType((Type)entry.Key); + ICollection namesObj = + (ICollection)entry.Value; + ICollection infos = + new HashSet(); + foreach (object name in namesObj) + { + ObjectClassInfo objectClass = CollectionUtil.GetValue(objectClassesByName, (string)name, null); + if (objectClass != null) + { + infos.Add(objectClass); + } + } + objectClassesByOperation[op] = infos; + } + IDictionary optionsByOperationObj = + (IDictionary)decoder.ReadObjectField("optionsByOperation", null, null); + IDictionary, ICollection> + optionsByOperation = + new Dictionary, ICollection>(); + foreach (KeyValuePair entry in optionsByOperationObj) + { + SafeType op = SafeType.ForRawType((Type)entry.Key); + ICollection namesObj = + (ICollection)entry.Value; + ICollection infos = + new HashSet(); + foreach (object name in namesObj) + { + OperationOptionInfo info = CollectionUtil.GetValue(optionsByName, (string)name, null); + if (info != null) + { + infos.Add(info); + } + } + optionsByOperation[op] = infos; + } + return new Schema(objectClasses, + operationOptions, + objectClassesByOperation, + optionsByOperation); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + Schema val = (Schema)obj; + encoder.WriteObjectField("ObjectClassInfos", val.ObjectClassInfo, true); + encoder.WriteObjectField("OperationOptionInfos", val.OperationOptionInfo, true); + IDictionary> + objectClassNamesByOperation = new Dictionary>(); + IDictionary> + optionNamesByOperation = new Dictionary>(); + + + foreach (KeyValuePair, ICollection> + entry in val.SupportedObjectClassesByOperation) + { + ICollection value = entry.Value; + ICollection names = new HashSet(); + foreach (ObjectClassInfo info in value) + { + names.Add(info.ObjectType); + } + objectClassNamesByOperation[entry.Key.RawType] = names; + } + foreach (KeyValuePair, ICollection> + entry in val.SupportedOptionsByOperation) + { + ICollection value = entry.Value; + ICollection names = new HashSet(); + foreach (OperationOptionInfo info in value) + { + names.Add(info.Name); + } + optionNamesByOperation[entry.Key.RawType] = names; + } + encoder.WriteObjectField("objectClassesByOperation", objectClassNamesByOperation, false); + encoder.WriteObjectField("optionsByOperation", optionNamesByOperation, false); + } + } + private class UidHandler + : AbstractObjectSerializationHandler + { + public UidHandler() + : base(typeof(Uid), "Uid") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + String val = decoder.ReadStringField("uid", null); + String revision = decoder.ReadStringField("revision", null); + if (null == revision) + { + return new Uid(val); + } + else + { + return new Uid(val, revision); + } + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + Uid val = (Uid)obj; + encoder.WriteStringField("uid", val.GetUidValue()); + encoder.WriteStringField("revision", val.Revision); + } + } + private class ScriptHandler : AbstractObjectSerializationHandler + { + public ScriptHandler() + : base(typeof(Script), "Script") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + ScriptBuilder builder = new ScriptBuilder(); + builder.ScriptLanguage = decoder.ReadStringField("scriptLanguage", null); + builder.ScriptText = (String)decoder.ReadObjectField("scriptText", typeof(string), null); + return builder.Build(); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + Script val = (Script)obj; + encoder.WriteStringField("scriptLanguage", val.ScriptLanguage); + encoder.WriteObjectField("scriptText", val.ScriptText, true); + } + } + private class ScriptContextHandler : AbstractObjectSerializationHandler + { + public ScriptContextHandler() + : base(typeof(ScriptContext), "ScriptContext") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + String scriptLanguage = + decoder.ReadStringField("scriptLanguage", null); + IDictionary arguments = + (IDictionary)decoder.ReadObjectField("scriptArguments", null, null); + String scriptText = + (String)decoder.ReadObjectField("scriptText", typeof(string), null); + IDictionary arguments2 = + CollectionUtil.NewDictionary(arguments); + return new ScriptContext(scriptLanguage, scriptText, arguments2); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + ScriptContext val = (ScriptContext)obj; + encoder.WriteStringField("scriptLanguage", val.ScriptLanguage); + encoder.WriteObjectField("scriptArguments", val.ScriptArguments, false); + encoder.WriteObjectField("scriptText", val.ScriptText, true); + } + } + private class OperationOptionsHandler : AbstractObjectSerializationHandler + { + public OperationOptionsHandler() + : base(typeof(OperationOptions), "OperationOptions") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + IDictionary options = + (IDictionary)decoder.ReadObjectField("options", null, null); + IDictionary options2 = + CollectionUtil.NewDictionary(options); + return new OperationOptions(options2); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + OperationOptions val = (OperationOptions)obj; + encoder.WriteObjectField("options", val.Options, false); + } + } + private class SearchResultHandler : AbstractObjectSerializationHandler + { + public SearchResultHandler() + : base(typeof(SearchResult), "SearchResult") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + String pagedResultsCookie = decoder.ReadStringField("pagedResultsCookie", null); + int totalPagedResults = decoder.ReadIntField("totalPagedResults", -1); + int remainingPagedResults = decoder.ReadIntField("remainingPagedResults", -1); + SearchResult.CountPolicy policy = + (SearchResult.CountPolicy) + decoder.ReadObjectField("CountPolicy", typeof (SearchResult.CountPolicy), null); + return new SearchResult(pagedResultsCookie, policy, totalPagedResults, remainingPagedResults); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + SearchResult val = (SearchResult)obj; + encoder.WriteStringField("pagedResultsCookie", val.PagedResultsCookie); + encoder.WriteIntField("totalPagedResults", val.TotalPagedResults); + encoder.WriteIntField("remainingPagedResults", val.RemainingPagedResults); + encoder.WriteObjectField("CountPolicy", val.TotalPagedResultsPolicy, true); + } + } + private class SortKeyHandler : AbstractObjectSerializationHandler + { + public SortKeyHandler() + : base(typeof(Org.IdentityConnectors.Framework.Common.Objects.SortKey), "SortKey") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + return new Org.IdentityConnectors.Framework.Common.Objects.SortKey(decoder.ReadStringField("field", null), decoder.ReadBooleanField("isAscending", true)); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + Org.IdentityConnectors.Framework.Common.Objects.SortKey val = (Org.IdentityConnectors.Framework.Common.Objects.SortKey)obj; + encoder.WriteStringField("field", val.Field); + encoder.WriteBooleanField("isAscending", val.IsAscendingOrder()); + } + } + private class OperationOptionInfoHandler : AbstractObjectSerializationHandler + { + public OperationOptionInfoHandler() + : base(typeof(OperationOptionInfo), "OperationOptionInfo") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + String name = decoder.ReadStringField("name", null); + Type type = decoder.ReadClassField("type", null); + return new OperationOptionInfo(name, type); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + OperationOptionInfo val = (OperationOptionInfo)obj; + encoder.WriteStringField("name", val.Name); + encoder.WriteClassField("type", val.OptionType); + } + } + private class SyncTokenHandler : AbstractObjectSerializationHandler + { + public SyncTokenHandler() + : base(typeof(SyncToken), "SyncToken") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + Object value = decoder.ReadObjectField("value", null, null); + return new SyncToken(value); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + SyncToken val = (SyncToken)obj; + encoder.WriteObjectField("value", val.Value, false); + } + } + private class SyncDeltaHandler : AbstractObjectSerializationHandler + { + public SyncDeltaHandler() + : base(typeof(SyncDelta), "SyncDelta") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + SyncDeltaBuilder builder = new SyncDeltaBuilder(); + builder.DeltaType = ((SyncDeltaType)decoder.ReadObjectField("SyncDeltaType", typeof(SyncDeltaType), null)); + builder.Token = ((SyncToken)decoder.ReadObjectField("SyncToken", typeof(SyncToken), null)); + builder.PreviousUid = ((Uid)decoder.ReadObjectField("PreviousUid", typeof(Uid), null)); + builder.ObjectClass = ((ObjectClass)decoder.ReadObjectField("ObjectClass", typeof(ObjectClass), null)); + builder.Uid = ((Uid)decoder.ReadObjectField("Uid", typeof(Uid), null)); + builder.Object = ((ConnectorObject)decoder.ReadObjectField("ConnectorObject", typeof(ConnectorObject), null)); + return builder.Build(); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + SyncDelta val = (SyncDelta)obj; + encoder.WriteObjectField("SyncDeltaType", val.DeltaType, true); + encoder.WriteObjectField("SyncToken", val.Token, true); + encoder.WriteObjectField("PreviousUid", val.PreviousUid, true); + encoder.WriteObjectField("ObjectClass", val.ObjectClass, true); + encoder.WriteObjectField("Uid", val.Uid, true); + encoder.WriteObjectField("ConnectorObject", val.Object, true); + } + } + private class QualifiedUidHandler : AbstractObjectSerializationHandler + { + public QualifiedUidHandler() + : base(typeof(QualifiedUid), "QualifiedUid") + { + + } + public override Object Deserialize(ObjectDecoder decoder) + { + ObjectClass objectClass = (ObjectClass)decoder.ReadObjectField("ObjectClass", typeof(ObjectClass), null); + Uid uid = (Uid)decoder.ReadObjectField("Uid", typeof(Uid), null); + return new QualifiedUid(objectClass, uid); + } + + public override void Serialize(Object obj, ObjectEncoder encoder) + { + QualifiedUid val = (QualifiedUid)obj; + encoder.WriteObjectField("ObjectClass", val.ObjectClass, true); + encoder.WriteObjectField("Uid", val.Uid, true); + } + } + } + #endregion + + #region FilterHandlers + internal static class FilterHandlers + { + public static readonly IList HANDLERS = + new List(); + static FilterHandlers() + { + HANDLERS.Add(new AndFilterHandler()); + HANDLERS.Add(new ContainsFilterHandler()); + HANDLERS.Add(new EndsWithFilterHandler()); + HANDLERS.Add(new EqualsFilterHandler()); + HANDLERS.Add(new ExtendedMatchFilterHandler()); + HANDLERS.Add(new GreaterThanFilterHandler()); + HANDLERS.Add(new GreaterThanOrEqualFilterHandler()); + HANDLERS.Add(new LessThanFilterHandler()); + HANDLERS.Add(new LessThanOrEqualFilterHandler()); + HANDLERS.Add(new NotFilterHandler()); + HANDLERS.Add(new OrFilterHandler()); + HANDLERS.Add(new PresenceFilterHandler()); + HANDLERS.Add(new StartsWithFilterHandler()); + HANDLERS.Add(new ContainsAllValuesFilterHandler()); + } + + private abstract class CompositeFilterHandler + : AbstractObjectSerializationHandler + where T : CompositeFilter + { + + protected CompositeFilterHandler(String typeName) + : base(typeof(T), typeName) + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + Filter left = (Filter)decoder.ReadObjectContents(0); + Filter right = (Filter)decoder.ReadObjectContents(1); + return CreateFilter(left, right); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + CompositeFilter val = (CompositeFilter)obj; + encoder.WriteObjectContents(val.Left); + encoder.WriteObjectContents(val.Right); + } + + protected abstract T CreateFilter(Filter left, Filter right); + } + + private abstract class AttributeFilterHandler + : AbstractObjectSerializationHandler + where T : AttributeFilter + { + + protected AttributeFilterHandler(String typeName) + : base(typeof(T), typeName) + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + ConnectorAttribute attribute = (ConnectorAttribute)decoder.ReadObjectField("attribute", null, null); + return CreateFilter(attribute); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + AttributeFilter val = (AttributeFilter)obj; + encoder.WriteObjectField("attribute", val.GetAttribute(), false); + } + + protected abstract T CreateFilter(ConnectorAttribute attribute); + } + + + + + private class AndFilterHandler : CompositeFilterHandler + { + public AndFilterHandler() + : base("AndFilter") + { + } + protected override AndFilter CreateFilter(Filter left, Filter right) + { + return new AndFilter(left, right); + } + } + + private class ContainsFilterHandler : AttributeFilterHandler + { + public ContainsFilterHandler() + : base("ContainsFilter") + { + } + protected override ContainsFilter CreateFilter(ConnectorAttribute attribute) + { + return new ContainsFilter(attribute); + } + } + + private class EndsWithFilterHandler : AttributeFilterHandler + { + public EndsWithFilterHandler() + : base("EndsWithFilter") + { + } + protected override EndsWithFilter CreateFilter(ConnectorAttribute attribute) + { + return new EndsWithFilter(attribute); + } + } + + private class EqualsFilterHandler : AttributeFilterHandler + { + public EqualsFilterHandler() + : base("EqualsFilter") + { + } + protected override EqualsFilter CreateFilter(ConnectorAttribute attribute) + { + return new EqualsFilter(attribute); + } + } + + private class ExtendedMatchFilterHandler + : AbstractObjectSerializationHandler + { + + public ExtendedMatchFilterHandler() + : base(typeof(ExtendedMatchFilter), "ExtendedMatchFilter") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + String op = decoder.ReadStringField("operator", null); + ConnectorAttribute attribute = (ConnectorAttribute)decoder.ReadObjectField("attribute", null, null); + return new ExtendedMatchFilter(op, attribute); + + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + ExtendedMatchFilter val = (ExtendedMatchFilter)obj; + encoder.WriteStringField("operator", val.Operator); + encoder.WriteObjectField("attribute", val.GetAttribute(), false); + + + } + + } + + private class GreaterThanFilterHandler : AttributeFilterHandler + { + public GreaterThanFilterHandler() + : base("GreaterThanFilter") + { + } + protected override GreaterThanFilter CreateFilter(ConnectorAttribute attribute) + { + return new GreaterThanFilter(attribute); + } + } + + private class GreaterThanOrEqualFilterHandler : AttributeFilterHandler + { + public GreaterThanOrEqualFilterHandler() + : base("GreaterThanOrEqualFilter") + { + } + protected override GreaterThanOrEqualFilter CreateFilter(ConnectorAttribute attribute) + { + return new GreaterThanOrEqualFilter(attribute); + } + } + private class LessThanFilterHandler : AttributeFilterHandler + { + public LessThanFilterHandler() + : base("LessThanFilter") + { + } + protected override LessThanFilter CreateFilter(ConnectorAttribute attribute) + { + return new LessThanFilter(attribute); + } + } + private class LessThanOrEqualFilterHandler : AttributeFilterHandler + { + public LessThanOrEqualFilterHandler() + : base("LessThanOrEqualFilter") + { + } + protected override LessThanOrEqualFilter CreateFilter(ConnectorAttribute attribute) + { + return new LessThanOrEqualFilter(attribute); + } + } + private class NotFilterHandler + : AbstractObjectSerializationHandler + { + + public NotFilterHandler() + : base(typeof(NotFilter), "NotFilter") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + Filter filter = + (Filter)decoder.ReadObjectContents(0); + return new NotFilter(filter); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + NotFilter val = (NotFilter)obj; + encoder.WriteObjectContents(val.Filter); + } + + } + private class OrFilterHandler : CompositeFilterHandler + { + public OrFilterHandler() + : base("OrFilter") + { + } + protected override OrFilter CreateFilter(Filter left, Filter right) + { + return new OrFilter(left, right); + } + } + private class PresenceFilterHandler + : AbstractObjectSerializationHandler + { + + public PresenceFilterHandler() + : base(typeof(PresenceFilter), "PresenceFilter") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + String name = decoder.ReadStringField("name", null); + return new PresenceFilter(name); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + PresenceFilter val = (PresenceFilter)obj; + encoder.WriteStringField("name", val.Name); + } + + } + private class StartsWithFilterHandler : AttributeFilterHandler + { + public StartsWithFilterHandler() + : base("StartsWithFilter") + { + } + protected override StartsWithFilter CreateFilter(ConnectorAttribute attribute) + { + return new StartsWithFilter(attribute); + } + } + + private class ContainsAllValuesFilterHandler : AttributeFilterHandler + { + public ContainsAllValuesFilterHandler() + : base("ContainsAllValuesFilter") + { + } + protected override ContainsAllValuesFilter CreateFilter(ConnectorAttribute attribute) + { + return new ContainsAllValuesFilter(attribute); + } + } + } + #endregion + + #region MessageHandlers + internal static class MessageHandlers + { + public static readonly IList HANDLERS = + new List(); + static MessageHandlers() + { + HANDLERS.Add(new HelloRequestHandler()); + HANDLERS.Add(new HelloResponseHandler()); + HANDLERS.Add(new OperationRequestHandler()); + HANDLERS.Add(new OperationResponseEndHandler()); + HANDLERS.Add(new OperationResponsePartHandler()); + HANDLERS.Add(new OperationRequestMoreDataHandler()); + HANDLERS.Add(new OperationRequestStopDataHandler()); + HANDLERS.Add(new OperationResponsePauseHandler()); + HANDLERS.Add(new EchoMessageHandler()); + } + private class HelloRequestHandler + : AbstractObjectSerializationHandler + { + + public HelloRequestHandler() + : base(typeof(HelloRequest), "HelloRequest") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + return new HelloRequest(decoder.ReadIntField("infoLevel", HelloRequest.CONNECTOR_INFO)); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + HelloRequest val = (HelloRequest)obj; + encoder.WriteIntField("infoLevel", val.GetInfoLevel()); + } + + } + private class HelloResponseHandler + : AbstractObjectSerializationHandler + { + + public HelloResponseHandler() + : base(typeof(HelloResponse), "HelloResponse") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + Exception exception = + (Exception)decoder.ReadObjectField("exception", null, null); + IDictionary serverInfoRaw = + (IDictionary)decoder.ReadObjectField("serverInfoMap", null, null); + IDictionary serverInfo = null; + if (null != serverInfoRaw) + { + serverInfo = new Dictionary(serverInfoRaw.Count); + foreach (KeyValuePair entry in serverInfoRaw) + { + serverInfo.Add(entry.Key.ToString(), entry.Value); + } + } + + IList connectorInfosObj = + (IList)decoder.ReadObjectField("ConnectorInfos", typeof(IList), null); + IList connectorInfos = + CollectionUtil.NewList(connectorInfosObj); + IList connectorKeysObj = + (IList)decoder.ReadObjectField("ConnectorKeys", typeof(IList), null); + IList connectorKeys = CollectionUtil.NewList(connectorKeysObj); + return new HelloResponse(exception, serverInfo, connectorKeys, connectorInfos); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + HelloResponse val = (HelloResponse)obj; + encoder.WriteObjectField("exception", val.Exception, false); + encoder.WriteObjectField("serverInfoMap", val.ServerInfo, false); + encoder.WriteObjectField("ConnectorInfos", val.ConnectorInfos, true); + encoder.WriteObjectField("ConnectorKeys", val.ConnectorKeys, true); + } + + } + private class OperationRequestHandler + : AbstractObjectSerializationHandler + { + + public OperationRequestHandler() + : base(typeof(OperationRequest), "OperationRequest") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + ConnectorKey connectorKey = + (ConnectorKey)decoder.ReadObjectField("ConnectorKey", typeof(ConnectorKey), null); + String connectorFacadeKey = + decoder.ReadStringField("connectorFacadeKey", null); + Type operation = + decoder.ReadClassField("operation", null); + string operationMethodName = + decoder.ReadStringField("operationMethodName", null); + IList arguments = (IList) + decoder.ReadObjectField("Arguments", typeof(IList), null); + return new OperationRequest(connectorKey, + connectorFacadeKey, + SafeType.ForRawType(operation), + operationMethodName, + arguments); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + OperationRequest val = + (OperationRequest)obj; + encoder.WriteClassField("operation", + val.Operation.RawType); + encoder.WriteStringField("operationMethodName", + val.OperationMethodName); + encoder.WriteStringField("connectorFacadeKey", + val.ConnectorFacadeKey); + encoder.WriteObjectField("ConnectorKey", + val.ConnectorKey, true); + encoder.WriteObjectField("Arguments", + val.Arguments, true); + } + + } + private class OperationResponseEndHandler + : AbstractObjectSerializationHandler + { + + public OperationResponseEndHandler() + : base(typeof(OperationResponseEnd), "OperationResponseEnd") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + return new OperationResponseEnd(); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + } + } + private class OperationResponsePartHandler + : AbstractObjectSerializationHandler + { + + public OperationResponsePartHandler() + : base(typeof(OperationResponsePart), "OperationResponsePart") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + Exception exception = + (Exception)decoder.ReadObjectField("exception", null, null); + Object result = + decoder.ReadObjectField("result", null, null); + + return new OperationResponsePart(exception, result); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + OperationResponsePart val = (OperationResponsePart)obj; + encoder.WriteObjectField("exception", val.Exception, false); + encoder.WriteObjectField("result", val.Result, false); + } + } + private class OperationRequestMoreDataHandler + : AbstractObjectSerializationHandler + { + + public OperationRequestMoreDataHandler() + : base(typeof(OperationRequestMoreData), "OperationRequestMoreData") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + return new OperationRequestMoreData(); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + } + } + private class OperationRequestStopDataHandler + : AbstractObjectSerializationHandler + { + + public OperationRequestStopDataHandler() + : base(typeof(OperationRequestStopData), "OperationRequestStopData") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + return new OperationRequestStopData(); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + } + } + private class OperationResponsePauseHandler + : AbstractObjectSerializationHandler + { + + public OperationResponsePauseHandler() + : base(typeof(OperationResponsePause), "OperationResponsePause") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + return new OperationResponsePause(); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + } + } + private class EchoMessageHandler + : AbstractObjectSerializationHandler + { + + public EchoMessageHandler() + : base(typeof(EchoMessage), "EchoMessage") + { + } + + + public override sealed Object Deserialize(ObjectDecoder decoder) + { + return new EchoMessage(decoder.ReadObjectField("value", null, null), + (String)decoder.ReadObjectField("objectXml", typeof(string), null)); + } + + public override sealed void Serialize(Object obj, ObjectEncoder encoder) + { + EchoMessage val = (EchoMessage)obj; + encoder.WriteObjectField("value", val.Object, false); + encoder.WriteObjectField("objectXml", val.ObjectXml, true); + } + } + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/SerializerBinary.cs b/dotnet/framework/FrameworkInternal/SerializerBinary.cs new file mode 100644 index 00000000..ce554b96 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/SerializerBinary.cs @@ -0,0 +1,908 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Serializer; +using System.IO; +using System.Text; +namespace Org.IdentityConnectors.Framework.Impl.Serializer.Binary +{ + internal class InternalEncoder + { + /// + /// Mapping from type name to the ID we serialize so we only have to + /// + private IDictionary _constantPool = + new Dictionary(); + + private IList _constantBuffer = new List(); + + private IList _outputBufferStack = new List(); + internal Stream _rootOutput; + private bool _firstObject = true; + + public InternalEncoder(Stream output) + { + _rootOutput = output; + } + + public void WriteObject(ObjectEncoder encoder, Object obj) + { + + if (_firstObject) + { + WriteInt(BinaryObjectEncoder.OBJECT_MAGIC); + WriteInt(BinaryObjectEncoder.ENCODING_VERSION); + _firstObject = false; + } + + MemoryStream objectBuffer = new MemoryStream(); + _outputBufferStack.Add(objectBuffer); + + if (obj == null) + { + WriteByte(BinaryObjectEncoder.OBJECT_TYPE_NULL); + } + else + { + Type clazz = obj.GetType(); + WriteClass(clazz); + ObjectSerializationHandler handler = + ObjectSerializerRegistry.GetHandlerByObjectType(clazz); + if (handler == null) + { + //we may have special handlers for certain types of arrays + //if handler is null, treat like any other array + if (obj is Array) + { + Array array = (Array)obj; + int length = array.Length; + for (int i = 0; i < length; i++) + { + Object val = array.GetValue(i); + StartAnonymousField(); + WriteObject(encoder, val); + EndField(); + } + } + else + { + throw new ConnectorException("No serializer for class: " + clazz); + } + } + else + { + handler.Serialize(obj, encoder); + } + } + + //write end-object into the current obj buffer + WriteByte(BinaryObjectEncoder.FIELD_TYPE_END_OBJECT); + + //pop the stack + _outputBufferStack.RemoveAt(_outputBufferStack.Count - 1); + + //it's a top-level object, flush the constant pool + if (_outputBufferStack.Count == 0) + { + WriteInt(_constantBuffer.Count); + foreach (String constant in _constantBuffer) + { + WriteString(constant, false); + WriteInt(_constantPool[constant]); + } + _constantBuffer.Clear(); + } + + //now write the actual object + objectBuffer.Close(); + byte[] bytes = objectBuffer.ToArray(); + WriteBytes(bytes); + } + + public void WriteClass(Type clazz) + { + ObjectSerializationHandler handler = + ObjectSerializerRegistry.GetHandlerByObjectType(clazz); + ObjectTypeMapper mapper = + ObjectSerializerRegistry.GetMapperByObjectType(clazz); + if (handler == null && clazz.IsArray) + { + //we may have special handlers for certain types of arrays + //if handler is null, treat like any other array + WriteByte(BinaryObjectEncoder.OBJECT_TYPE_ARRAY); + WriteClass(clazz.GetElementType()); + } + else if (mapper == null) + { + throw new ConnectorException("No serializer for class: " + clazz); + } + else + { + String typeName = mapper.HandledSerialType; + WriteByte(BinaryObjectEncoder.OBJECT_TYPE_CLASS); + WriteString(typeName, true); + } + } + + public void StartAnonymousField() + { + WriteByte(BinaryObjectEncoder.FIELD_TYPE_ANONYMOUS_FIELD); + MemoryStream buf = new MemoryStream(); + _outputBufferStack.Add(buf); + } + + public void StartField(String name) + { + WriteByte(BinaryObjectEncoder.FIELD_TYPE_NAMED_FIELD); + WriteString(name, true); + MemoryStream buf = new MemoryStream(); + _outputBufferStack.Add(buf); + } + + public void EndField() + { + MemoryStream buf = _outputBufferStack[_outputBufferStack.Count - 1]; + _outputBufferStack.RemoveAt(_outputBufferStack.Count - 1); + buf.Close(); + byte[] bytes = buf.ToArray(); + WriteByteArray(bytes); + } + + public void WriteInt(int v) + { + Stream output = GetCurrentOutput(); + output.WriteByte((byte)(0xff & (v >> 24))); + output.WriteByte((byte)(0xff & (v >> 16))); + output.WriteByte((byte)(0xff & (v >> 8))); + output.WriteByte((byte)(0xff & v)); + } + + public void WriteLong(long v) + { + Stream output = GetCurrentOutput(); + output.WriteByte((byte)(0xff & (v >> 56))); + output.WriteByte((byte)(0xff & (v >> 48))); + output.WriteByte((byte)(0xff & (v >> 40))); + output.WriteByte((byte)(0xff & (v >> 32))); + output.WriteByte((byte)(0xff & (v >> 24))); + output.WriteByte((byte)(0xff & (v >> 16))); + output.WriteByte((byte)(0xff & (v >> 8))); + output.WriteByte((byte)(0xff & v)); + } + + public void WriteDouble(double l) + { + long val = BitConverter.DoubleToInt64Bits(l); + WriteLong(val); + } + + public void WriteByteArray(byte[] v) + { + WriteInt(v.Length); + WriteBytes(v); + } + + public void WriteByte(byte b) + { + GetCurrentOutput().WriteByte(b); + } + + public void WriteBoolean(bool b) + { + WriteByte(b ? (byte)1 : (byte)0); + } + + public void WriteString(String str, bool intern) + { + if (intern) + { + int code = InternIdentifier(str); + WriteInt(code); + return; + } + byte[] bytes = Encoding.UTF8.GetBytes(str); + WriteByteArray(bytes); + } + + private int InternIdentifier(String name) + { + int code = CollectionUtil.GetValue(_constantPool, name, -1); + if (code == -1) + { + code = _constantPool.Count; + _constantPool[name] = code; + _constantBuffer.Add(name); + } + return code; + } + + private void WriteBytes(byte[] v) + { + //only write if length > 0 - C# seems to have a problem with + //zero-length byte arrays + if (v.Length > 0) + { + GetCurrentOutput().Write(v, 0, v.Length); + } + } + + private Stream GetCurrentOutput() + { + if (_outputBufferStack.Count == 0) + { + return _rootOutput; + } + else + { + MemoryStream buf = _outputBufferStack[_outputBufferStack.Count - 1]; + return buf; + } + } + } + + internal class BinaryObjectEncoder : ObjectEncoder, BinaryObjectSerializer + { + public const int ENCODING_VERSION = 2; + + public const int OBJECT_MAGIC = 0xFAFB; + + public const byte OBJECT_TYPE_NULL = 60; + public const byte OBJECT_TYPE_CLASS = 61; + public const byte OBJECT_TYPE_ARRAY = 62; + + public const byte FIELD_TYPE_ANONYMOUS_FIELD = 70; + public const byte FIELD_TYPE_NAMED_FIELD = 71; + public const byte FIELD_TYPE_END_OBJECT = 72; + + + private InternalEncoder _internalEncoder; + + public BinaryObjectEncoder(Stream output) + { + _internalEncoder = new InternalEncoder(new BufferedStream(output, 4096)); + } + + public void Flush() + { + _internalEncoder._rootOutput.Flush(); + } + + public void Close() + { + _internalEncoder._rootOutput.Close(); + } + + public void WriteObject(Object o) + { + _internalEncoder.WriteObject(this, o); + } + + public void WriteBooleanContents(bool v) + { + _internalEncoder.StartAnonymousField(); + _internalEncoder.WriteBoolean(v); + _internalEncoder.EndField(); + } + + public void WriteBooleanField(String fieldName, bool v) + { + _internalEncoder.StartField(fieldName); + _internalEncoder.WriteBoolean(v); + _internalEncoder.EndField(); + } + + public void WriteByteContents(byte v) + { + _internalEncoder.StartAnonymousField(); + _internalEncoder.WriteByte(v); + _internalEncoder.EndField(); + } + + public void WriteByteArrayContents(byte[] v) + { + _internalEncoder.StartAnonymousField(); + _internalEncoder.WriteByteArray(v); + _internalEncoder.EndField(); + } + + public void WriteClassContents(Type v) + { + _internalEncoder.StartAnonymousField(); + _internalEncoder.WriteClass(v); + _internalEncoder.EndField(); + } + + public void WriteClassField(string fieldName, Type v) + { + if (v != null) + { + _internalEncoder.StartField(fieldName); + _internalEncoder.WriteClass(v); + _internalEncoder.EndField(); + } + } + + public void WriteDoubleContents(double v) + { + _internalEncoder.StartAnonymousField(); + _internalEncoder.WriteDouble(v); + _internalEncoder.EndField(); + } + + public void WriteDoubleField(String fieldName, double v) + { + _internalEncoder.StartField(fieldName); + _internalEncoder.WriteDouble(v); + _internalEncoder.EndField(); + } + + public void WriteFloatContents(float v) + { + _internalEncoder.StartAnonymousField(); + //write as double since C# only knows how to deal with that + _internalEncoder.WriteDouble((double)v); + _internalEncoder.EndField(); + } + + public void WriteFloatField(String fieldName, float v) + { + _internalEncoder.StartField(fieldName); + //write as double since C# only knows how to deal with that + _internalEncoder.WriteDouble((double)v); + _internalEncoder.EndField(); + } + + public void WriteIntContents(int v) + { + _internalEncoder.StartAnonymousField(); + _internalEncoder.WriteInt(v); + _internalEncoder.EndField(); + } + + public void WriteIntField(String fieldName, int v) + { + _internalEncoder.StartField(fieldName); + _internalEncoder.WriteInt(v); + _internalEncoder.EndField(); + } + + public void WriteLongContents(long v) + { + _internalEncoder.StartAnonymousField(); + _internalEncoder.WriteLong(v); + _internalEncoder.EndField(); + } + + public void WriteLongField(String fieldName, long v) + { + _internalEncoder.StartField(fieldName); + _internalEncoder.WriteLong(v); + _internalEncoder.EndField(); + } + + public void WriteObjectContents(Object obj) + { + _internalEncoder.StartAnonymousField(); + _internalEncoder.WriteObject(this, obj); + _internalEncoder.EndField(); + } + + public void WriteObjectField(String fieldName, Object obj, bool inline) + { + _internalEncoder.StartField(fieldName); + _internalEncoder.WriteObject(this, obj); + _internalEncoder.EndField(); + } + + public void WriteStringContents(String str) + { + _internalEncoder.StartAnonymousField(); + _internalEncoder.WriteString(str, false); + _internalEncoder.EndField(); + } + + public void WriteStringField(String fieldName, String val) + { + if (val != null) + { + _internalEncoder.StartField(fieldName); + _internalEncoder.WriteString(val, false); + _internalEncoder.EndField(); + } + } + } + + internal class ReadState + { + public IDictionary objectFields = new Dictionary(); + public IList anonymousFields = new List(); + public Stream currentInput; + public ReadState() + { + } + public bool StartField(String name) + { + currentInput = null; + byte[] content = CollectionUtil.GetValue(objectFields, name, null); + if (content == null) + { + return false; + } + else + { + currentInput = new MemoryStream(content); + return true; + } + } + public void StartAnonymousField(int index) + { + if (index >= anonymousFields.Count) + { + throw new ConnectorException("Anonymous content not found"); + } + currentInput = new MemoryStream(anonymousFields[index]); + } + } + + internal class InternalDecoder + { + private readonly byte[] _int_buf = new byte[4]; + private readonly byte[] _long_buf = new byte[8]; + private readonly byte[] _byte_buf = new byte[1]; + + private bool _firstObject = true; + + private readonly IDictionary _constantPool = + new Dictionary(); + + private readonly IList _readStateStack = new List(); + internal readonly Stream _rootInput; + + public InternalDecoder(Stream input) + { + _rootInput = input; + } + + public Object ReadObject(ObjectDecoder decoder) + { + + if (_firstObject) + { + int magic = ReadInt(); + if (magic != BinaryObjectEncoder.OBJECT_MAGIC) + { + throw new ConnectorException("Bad magic number: " + magic); + } + int version = ReadInt(); + if (version != BinaryObjectEncoder.ENCODING_VERSION) + { + throw new ConnectorException("Unexpected version: " + version); + } + _firstObject = false; + } + + //if it's a top-level object, it's proceeded by a constant pool + if (_readStateStack.Count == 0) + { + int size = ReadInt(); + for (int i = 0; i < size; i++) + { + String constant = ReadString(false); + int code = ReadInt(); + _constantPool[code] = constant; + } + } + + Type clazz = ReadClass(); + + ReadState state = new ReadState(); + while (true) + { + byte type = ReadByte(); + if (type == BinaryObjectEncoder.FIELD_TYPE_END_OBJECT) + { + break; + } + else if (type == BinaryObjectEncoder.FIELD_TYPE_ANONYMOUS_FIELD) + { + byte[] bytes = ReadByteArray(); + state.anonymousFields.Add(bytes); + } + else if (type == BinaryObjectEncoder.FIELD_TYPE_NAMED_FIELD) + { + String fieldName = ReadString(true); + byte[] bytes = ReadByteArray(); + state.objectFields[fieldName] = bytes; + } + else + { + throw new ConnectorException("Unknown type: " + type); + } + } + _readStateStack.Add(state); + + Object rv; + if (clazz == null) + { + rv = null; + } + else + { + ObjectSerializationHandler handler = + ObjectSerializerRegistry.GetHandlerByObjectType(clazz); + if (handler == null) + { + //we may have special handlers for certain types of arrays + //if handler is null, treat like any other array + if (clazz.IsArray) + { + int length = GetNumAnonymousFields(); + Array array = Array.CreateInstance(clazz.GetElementType(), + length); + for (int i = 0; i < length; i++) + { + StartAnonymousField(i); + Object element = ReadObject(decoder); + array.SetValue(element, i); + } + rv = array; + } + else + { + throw new ConnectorException("No deserializer for type: " + clazz); + } + } + else + { + rv = handler.Deserialize(decoder); + } + } + _readStateStack.RemoveAt(_readStateStack.Count - 1); + return rv; + } + + public Type ReadClass() + { + int type = ReadByte(); + if (type == BinaryObjectEncoder.OBJECT_TYPE_NULL) + { + return null; + } + else if (type == BinaryObjectEncoder.OBJECT_TYPE_ARRAY) + { + Type componentClass = ReadClass(); + return componentClass.MakeArrayType(); + } + else if (type == BinaryObjectEncoder.OBJECT_TYPE_CLASS) + { + String typeName = ReadString(true); + ObjectTypeMapper mapper = + ObjectSerializerRegistry.GetMapperBySerialType(typeName); + if (mapper == null) + { + throw new ConnectorException("No deserializer for type: " + typeName); + } + return mapper.HandledObjectType; + } + else + { + throw new ConnectorException("Bad type value: " + type); + } + } + + public int GetNumAnonymousFields() + { + ReadState readState = _readStateStack[_readStateStack.Count - 1]; + return readState.anonymousFields.Count; + } + + public void StartAnonymousField(int index) + { + ReadState readState = _readStateStack[_readStateStack.Count - 1]; + readState.StartAnonymousField(index); + } + + public bool StartField(String name) + { + ReadState readState = _readStateStack[_readStateStack.Count - 1]; + return readState.StartField(name); + } + + public int ReadInt() + { + ReadByteArrayFully(_int_buf); + return (((_int_buf[0] & 0xff) << 24) | ((_int_buf[1] & 0xff) << 16) | + ((_int_buf[2] & 0xff) << 8) | (_int_buf[3] & 0xff)); + } + + public long ReadLong() + { + ReadByteArrayFully(_long_buf); + return (((long)(_long_buf[0] & 0xff) << 56) | + ((long)(_long_buf[1] & 0xff) << 48) | + ((long)(_long_buf[2] & 0xff) << 40) | + ((long)(_long_buf[3] & 0xff) << 32) | + ((long)(_long_buf[4] & 0xff) << 24) | + ((long)(_long_buf[5] & 0xff) << 16) | + ((long)(_long_buf[6] & 0xff) << 8) | + ((_long_buf[7] & 0xff))); + } + + public double ReadDouble() + { + long v = ReadLong(); + return BitConverter.Int64BitsToDouble(v); + } + + public byte[] ReadByteArray() + { + int len = ReadInt(); + byte[] bytes = new byte[len]; + ReadByteArrayFully(bytes); + return bytes; + } + + public byte ReadByte() + { + ReadByteArrayFully(_byte_buf); + return _byte_buf[0]; + } + + public bool ReadBoolean() + { + byte b = ReadByte(); + return b != 0; + } + + public String ReadString(bool interned) + { + if (interned) + { + int code = ReadInt(); + String name = CollectionUtil.GetValue(_constantPool, code, null); + if (name == null) + { + throw new ConnectorException("Undeclared code: " + code); + } + return name; + } + byte[] bytes = ReadByteArray(); + return Encoding.UTF8.GetString(bytes); + } + + private Stream GetCurrentInput() + { + if (_readStateStack.Count > 0) + { + ReadState state = _readStateStack[_readStateStack.Count - 1]; + return state.currentInput; + } + else + { + return _rootInput; + } + } + + private void ReadByteArrayFully(byte[] bytes) + { + int pos = 0; + while (pos < bytes.Length) + { + int count = GetCurrentInput().Read(bytes, pos, bytes.Length - pos); + if (count <= 0) + { + throw new EndOfStreamException(); + } + pos += count; + } + } + } + + internal class BinaryObjectDecoder : ObjectDecoder, BinaryObjectDeserializer + { + + private InternalDecoder _internalDecoder; + + public BinaryObjectDecoder(Stream inp) + { + _internalDecoder = new InternalDecoder(new BufferedStream(inp, 4096)); + } + + public void Close() + { + _internalDecoder._rootInput.Close(); + } + + public Object ReadObject() + { + return _internalDecoder.ReadObject(this); + } + + public bool ReadBooleanContents() + { + _internalDecoder.StartAnonymousField(0); + return _internalDecoder.ReadBoolean(); + } + + public bool ReadBooleanField(String fieldName, bool dflt) + { + if (_internalDecoder.StartField(fieldName)) + { + return _internalDecoder.ReadBoolean(); + } + else + { + return dflt; + } + } + + public byte[] ReadByteArrayContents() + { + _internalDecoder.StartAnonymousField(0); + return _internalDecoder.ReadByteArray(); + } + + public byte ReadByteContents() + { + _internalDecoder.StartAnonymousField(0); + return _internalDecoder.ReadByte(); + } + + public Type ReadClassContents() + { + _internalDecoder.StartAnonymousField(0); + return _internalDecoder.ReadClass(); + } + + public Type ReadClassField(string fieldName, Type dflt) + { + if (_internalDecoder.StartField(fieldName)) + { + return _internalDecoder.ReadClass(); + } + else + { + return dflt; + } + } + + public double ReadDoubleContents() + { + _internalDecoder.StartAnonymousField(0); + return _internalDecoder.ReadDouble(); + } + + public double ReadDoubleField(String fieldName, double dflt) + { + if (_internalDecoder.StartField(fieldName)) + { + return ReadDoubleContents(); + } + else + { + return dflt; + } + } + + public float ReadFloatContents() + { + _internalDecoder.StartAnonymousField(0); + //read as double since C# only knows how to deal with that + return (float)_internalDecoder.ReadDouble(); + } + + public float ReadFloatField(String fieldName, float dflt) + { + if (_internalDecoder.StartField(fieldName)) + { + //read as double since C# only knows how to deal with that + return (float)_internalDecoder.ReadDouble(); + } + else + { + return dflt; + } + } + + public int ReadIntContents() + { + _internalDecoder.StartAnonymousField(0); + return _internalDecoder.ReadInt(); + } + + public int ReadIntField(String fieldName, int dflt) + { + if (_internalDecoder.StartField(fieldName)) + { + return _internalDecoder.ReadInt(); + } + else + { + return dflt; + } + } + + public long ReadLongContents() + { + _internalDecoder.StartAnonymousField(0); + return _internalDecoder.ReadLong(); + } + + public long ReadLongField(String fieldName, long dflt) + { + if (_internalDecoder.StartField(fieldName)) + { + return _internalDecoder.ReadLong(); + } + else + { + return dflt; + } + } + + public int GetNumSubObjects() + { + return _internalDecoder.GetNumAnonymousFields(); + } + + public Object ReadObjectContents(int index) + { + _internalDecoder.StartAnonymousField(index); + return _internalDecoder.ReadObject(this); + } + + public Object ReadObjectField(String fieldName, Type expected, Object dflt) + { + if (_internalDecoder.StartField(fieldName)) + { + return _internalDecoder.ReadObject(this); + } + else + { + return dflt; + } + } + + public String ReadStringContents() + { + _internalDecoder.StartAnonymousField(0); + return _internalDecoder.ReadString(false); + } + + public String ReadStringField(String fieldName, String dflt) + { + if (_internalDecoder.StartField(fieldName)) + { + return _internalDecoder.ReadString(false); + } + else + { + return dflt; + } + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/SerializerXml.cs b/dotnet/framework/FrameworkInternal/SerializerXml.cs new file mode 100644 index 00000000..e219a915 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/SerializerXml.cs @@ -0,0 +1,952 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using System.IO; +using System.Resources; +using System.Text; +using System.Net; +using System.Xml; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Serializer; +namespace Org.IdentityConnectors.Framework.Impl.Serializer.Xml +{ + public class XmlObjectEncoder : ObjectEncoder + { + private StringBuilder _rootBuilder; + private XmlWriter _writer; + + public XmlObjectEncoder(StringBuilder builder) + { + Assertions.NullCheck(builder, "builder"); + _rootBuilder = builder; + } + + public String WriteObject(Object o) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.OmitXmlDeclaration = true; + _writer = XmlWriter.Create(_rootBuilder, settings); + String rv = WriteObjectInternal(o, false); + _writer.Close(); + return rv; + } + + public void WriteBooleanContents(bool v) + { + WriteStringContentsInternal(EncodeBoolean(v)); + } + + public void WriteBooleanField(String fieldName, bool v) + { + WriteAttributeInternal(fieldName, EncodeBoolean(v)); + } + + public void WriteByteContents(byte v) + { + WriteStringContentsInternal(EncodeByte(v)); + } + + public void WriteByteArrayContents(byte[] v) + { + WriteStringContentsInternal(EncodeByteArray(v)); + } + + public void WriteClassContents(Type v) + { + WriteStringContentsInternal(EncodeClass(v)); + } + + public void WriteClassField(String name, Type v) + { + if (v != null) + { + WriteAttributeInternal(name, EncodeClass(v)); + } + } + + public void WriteDoubleContents(double v) + { + WriteStringContentsInternal(EncodeDouble(v)); + } + + public void WriteDoubleField(String fieldName, double v) + { + WriteAttributeInternal(fieldName, EncodeDouble(v)); + } + + public void WriteFloatContents(float v) + { + WriteStringContentsInternal(EncodeFloat(v)); + } + + public void WriteFloatField(String fieldName, float v) + { + WriteAttributeInternal(fieldName, EncodeFloat(v)); + } + + public void WriteIntContents(int v) + { + WriteStringContentsInternal(EncodeInt(v)); + } + + public void WriteIntField(String fieldName, int v) + { + WriteAttributeInternal(fieldName, EncodeInt(v)); + } + + public void WriteLongContents(long v) + { + WriteStringContentsInternal(EncodeLong(v)); + } + + public void WriteLongField(String fieldName, long v) + { + WriteAttributeInternal(fieldName, EncodeLong(v)); + } + + public void WriteObjectContents(Object o) + { + WriteObjectInternal(o, false); + } + + public void WriteObjectField(String fieldName, Object obj, bool inline) + { + if (inline && obj == null) + { + return; //don't write anything + } + BeginElement(fieldName); + WriteObjectInternal(obj, inline); + EndElement(); + } + + public void WriteStringContents(String str) + { + WriteStringContentsInternal(str); + } + + public void WriteStringField(String fieldName, String str) + { + if (str != null) + { + WriteAttributeInternal(fieldName, str); + } + } + + internal static String EncodeBoolean(bool b) + { + return b.ToString(); + } + + private static String EncodeByte(byte singleByte) + { + return Convert.ToString(singleByte); + } + + private static String EncodeByteArray(byte[] bytes) + { + return Convert.ToBase64String(bytes); + } + + private static String EncodeClass(Type clazz) + { + ObjectSerializationHandler handler = + ObjectSerializerRegistry.GetHandlerByObjectType(clazz); + ObjectTypeMapper mapper = + ObjectSerializerRegistry.GetMapperByObjectType(clazz); + if (handler == null && clazz.IsArray) + { + //we may have special handlers for certain types of arrays + //if handler is null, treat like any other array + return EncodeClass(clazz.GetElementType()) + "[]"; + } + else if (mapper == null) + { + throw new ConnectorException("No serializer for class: " + clazz); + } + else + { + String typeName = mapper.HandledSerialType; + return typeName; + } + } + + internal static String EncodeDouble(double d) + { + return d.ToString("R"); + } + + internal static String EncodeFloat(float d) + { + return d.ToString("R"); + } + + internal static String EncodeInt(int d) + { + return d.ToString(); + } + + internal static String EncodeLong(long d) + { + return d.ToString(); + } + + /// + /// Writes the object + /// + /// + /// + /// The type name (regardless of whether it was inlined) + String WriteObjectInternal(Object obj, bool inline) + { + if (obj == null) + { + if (inline) + { + throw new ArgumentException("null cannot be inlined"); + } + BeginElement("null"); + EndElement(); + return "null"; + } + else + { + Type clazz = obj.GetType(); + ObjectSerializationHandler handler = + ObjectSerializerRegistry.GetHandlerByObjectType(clazz); + if (handler == null) + { + //we may have special handlers for certain types of arrays + //if handler is null, treat like any other array + if (clazz.IsArray) + { + if (!inline) + { + String componentTypeName = EncodeClass(clazz.GetElementType()); + BeginElement("Array"); + WriteAttributeInternal("componentType", componentTypeName); + } + Array array = (Array)obj; + int length = array.Length; + for (int i = 0; i < length; i++) + { + Object val = array.GetValue(i); + WriteObjectInternal(val, false); + } + if (!inline) + { + EndElement(); + } + return "Array"; + } + else + { + throw new ConnectorException("No serializer for class: " + clazz); + } + } + else + { + String typeName = EncodeClass(clazz); + if (!inline) + { + BeginElement(typeName); + } + handler.Serialize(obj, this); + if (!inline) + { + EndElement(); + } + return typeName; + } + } + + } + + ////////////////////////////////////////////////////////////////// + // + // xml encoding + // + ///////////////////////////////////////////////////////////////// + + private void BeginElement(String name) + { + _writer.WriteStartElement(name); + } + + private void EndElement() + { + _writer.WriteEndElement(); + } + private void WriteAttributeInternal(String fieldName, String str) + { + _writer.WriteAttributeString(fieldName, str); + } + private void WriteStringContentsInternal(String str) + { + _writer.WriteString(str); + } + } + + public class XmlObjectDecoder : ObjectDecoder + { + + private readonly XmlElement _node; + private readonly Type _expectedClass; + + public XmlObjectDecoder(XmlElement node, Type expectedClass) + { + _node = node; + _expectedClass = expectedClass; + } + + public Object ReadObject() + { + return ReadObjectInternal(); + } + + public bool ReadBooleanContents() + { + return DecodeBoolean(ReadStringContentsInternal()); + } + + public bool ReadBooleanField(String fieldName, bool dflt) + { + return DecodeBoolean(ReadStringAttributeInternal(fieldName, XmlObjectEncoder.EncodeBoolean(dflt))); + } + + public byte[] ReadByteArrayContents() + { + return DecodeByteArray(ReadStringContentsInternal()); + } + + public byte ReadByteContents() + { + return DecodeByte(ReadStringContentsInternal()); + } + + public Type ReadClassContents() + { + return DecodeClass(ReadStringContentsInternal()); + } + + public Type ReadClassField(String name, Type dflt) + { + String val = ReadStringAttributeInternal(name, null); + if (val == null) + { + return dflt; + } + else + { + return DecodeClass(val); + } + } + + public double ReadDoubleContents() + { + return DecodeDouble(ReadStringContentsInternal()); + } + + public double ReadDoubleField(String fieldName, double dflt) + { + return DecodeDouble(ReadStringAttributeInternal(fieldName, XmlObjectEncoder.EncodeDouble(dflt))); + } + + public float ReadFloatContents() + { + return DecodeFloat(ReadStringContentsInternal()); + } + + public float ReadFloatField(String fieldName, float dflt) + { + return DecodeFloat(ReadStringAttributeInternal(fieldName, XmlObjectEncoder.EncodeFloat(dflt))); + } + + public int ReadIntContents() + { + return DecodeInt(ReadStringContentsInternal()); + } + + public int ReadIntField(String fieldName, int dflt) + { + return DecodeInt(ReadStringAttributeInternal(fieldName, XmlObjectEncoder.EncodeInt(dflt))); + } + + public long ReadLongContents() + { + return DecodeLong(ReadStringContentsInternal()); + } + + public long ReadLongField(String fieldName, long dflt) + { + return DecodeLong(ReadStringAttributeInternal(fieldName, XmlObjectEncoder.EncodeLong(dflt))); + } + + public int GetNumSubObjects() + { + int count = 0; + for (XmlElement subElement = XmlUtil.GetFirstChildElement(_node); + subElement != null; + subElement = XmlUtil.GetNextElement(subElement)) + { + count++; + } + return count; + } + + public Object ReadObjectContents(int index) + { + XmlElement subElement = XmlUtil.GetFirstChildElement(_node); + for (int i = 0; i < index; i++) + { + subElement = XmlUtil.GetNextElement(subElement); + } + + if (subElement == null) + { + throw new ConnectorException("Missing subelement number: " + index); + } + + return new XmlObjectDecoder(subElement, null).ReadObject(); + } + + public Object ReadObjectField(String fieldName, Type expected, Object dflt) + { + XmlElement child = XmlUtil.FindImmediateChildElement(_node, fieldName); + if (child == null) + { + return dflt; + } + if (expected != null) + { + return new XmlObjectDecoder(child, expected).ReadObject(); + } + XmlElement subElement = XmlUtil.GetFirstChildElement(child); + if (subElement == null) + { + return dflt; + } + //if they specify null, don't apply defaults + return new XmlObjectDecoder(subElement, null).ReadObject(); + } + + public String ReadStringContents() + { + String rv = ReadStringContentsInternal(); + return rv == null ? "" : rv; + } + + public String ReadStringField(String fieldName, String dflt) + { + return ReadStringAttributeInternal(fieldName, dflt); + } + + private String ReadStringContentsInternal() + { + String xml = XmlUtil.GetContent(_node); + return xml; + } + + private String ReadStringAttributeInternal(String name, String dflt) + { + XmlAttribute attr = _node.GetAttributeNode(name); + if (attr == null) + { + return dflt; + } + return attr.Value; + } + + private bool DecodeBoolean(String v) + { + return Boolean.Parse(v); + } + + private byte[] DecodeByteArray(String base64) + { + return Convert.FromBase64String(base64); + } + + private byte DecodeByte(String v) + { + return Convert.ToByte(v); + } + + private Type DecodeClass(String type) + { + if (type.EndsWith("[]")) + { + String componentName = type.Substring(0, type.Length - "[]".Length); + Type componentClass = + DecodeClass(componentName); + Type arrayClass = + componentClass.MakeArrayType(); + return arrayClass; + } + else + { + ObjectTypeMapper mapper = + ObjectSerializerRegistry.GetMapperBySerialType(type); + if (mapper == null) + { + throw new ConnectorException("No deserializer for type: " + type); + } + Type clazz = mapper.HandledObjectType; + return clazz; + } + } + + private double DecodeDouble(String val) + { + return Double.Parse(val); + } + + private float DecodeFloat(String val) + { + return Single.Parse(val); + } + + private int DecodeInt(String val) + { + return Int32.Parse(val); + } + + private long DecodeLong(String val) + { + return Int64.Parse(val); + } + + private Object ReadObjectInternal() + { + if (_expectedClass != null) + { + ObjectSerializationHandler handler = + ObjectSerializerRegistry.GetHandlerByObjectType(_expectedClass); + if (handler == null) + { + if (_expectedClass.IsArray) + { + IList temp = new List(); + for (XmlElement child = XmlUtil.GetFirstChildElement(_node); child != null; + child = XmlUtil.GetNextElement(child)) + { + XmlObjectDecoder sub = new XmlObjectDecoder(child, null); + Object obj = sub.ReadObject(); + temp.Add(obj); + } + int length = temp.Count; + Array array = Array.CreateInstance(_expectedClass.GetElementType(), length); + for (int i = 0; i < length; i++) + { + Object element = temp[i]; + array.SetValue(element, i); + } + return array; + } + else + { + throw new ConnectorException("No deserializer for type: " + _expectedClass); + } + } + else + { + return handler.Deserialize(this); + } + } + else if (_node.LocalName.Equals("null")) + { + return null; + } + else if (_node.LocalName.Equals("Array")) + { + String componentType = XmlUtil.GetAttribute(_node, "componentType"); + if (componentType == null) + { + componentType = "Object"; + } + Type componentClass = DecodeClass(componentType); + IList temp = new List(); + for (XmlElement child = XmlUtil.GetFirstChildElement(_node); child != null; + child = XmlUtil.GetNextElement(child)) + { + XmlObjectDecoder sub = new XmlObjectDecoder(child, null); + Object obj = sub.ReadObject(); + temp.Add(obj); + } + int length = temp.Count; + Array array = Array.CreateInstance(componentClass, + length); + for (int i = 0; i < length; i++) + { + Object element = temp[i]; + array.SetValue(element, i); + } + return array; + } + else + { + Type clazz = + DecodeClass(_node.LocalName); + ObjectSerializationHandler handler = + ObjectSerializerRegistry.GetHandlerByObjectType(clazz); + if (handler == null) + { + throw new ConnectorException("No deserializer for type: " + clazz); + } + else + { + return handler.Deserialize(this); + } + } + } + } + + public class XmlObjectParser + { + public static void parse(TextReader inputSource, + XmlObjectResultsHandler handler, + bool validate) + { + XmlReaderSettings mySettings = + new XmlReaderSettings(); + if (validate) + { + mySettings.ValidationType = ValidationType.DTD; + } + mySettings.DtdProcessing = DtdProcessing.Parse; + //mySettings.ProhibitDtd = false; + mySettings.XmlResolver = new MyEntityResolver(validate); + XmlReader reader = XmlReader.Create(inputSource, mySettings); + MyParser parser = new MyParser(handler); + parser.Parse(reader); + } + + private class MyEntityResolver : XmlResolver + { + private readonly bool _validate; + + public MyEntityResolver(bool validate) + { + _validate = validate; + } + + public override Object GetEntity(Uri absoluteUri, string role, Type ofObject) + { + String text = null; + if (absoluteUri.AbsolutePath.EndsWith(XmlObjectSerializerImpl.CONNECTORS_DTD)) + { + if (!_validate) + { + text = ""; + } + else + { + text = GetDTD(); + } + } + if (text != null) + { + byte[] bytes = Encoding.UTF8.GetBytes(text); + return new MemoryStream(bytes); + } + return null; + } + + public override ICredentials Credentials + { + set + { + + } + } + private static String GetDTD() + { + ResourceManager manager = + new ResourceManager("Org.IdentityConnectors.Resources", + typeof(XmlObjectParser).Assembly); + String contents = (String)manager.GetObject(XmlObjectSerializerImpl.CONNECTORS_DTD); + return contents; + } + } + + private class MyParser + { + /// + /// The document for the current top-level element. + /// + /// + /// with each top-level element, + /// we discard the previous to avoid accumulating memory + /// + private XmlDocument _currentTopLevelElementDocument; + + + /// + /// Stack of elements we are creating + /// + private IList _elementStack = new List(10); + + /// + /// Results handler that we write our objects to + /// + private readonly XmlObjectResultsHandler _handler; + + /// + /// Is the handler still handing + /// + private bool _stillHandling = true; + + + public MyParser(XmlObjectResultsHandler handler) + { + _handler = handler; + } + + public void Parse(XmlReader reader) + { + while (_stillHandling && reader.Read()) + { + XmlNodeType nodeType = reader.NodeType; + switch (nodeType) + { + case XmlNodeType.Element: + StartElement(reader.LocalName); + bool empty = reader.IsEmptyElement; + if (reader.MoveToFirstAttribute()) + { + AddAttribute(reader.LocalName, reader.Value); + while (reader.MoveToNextAttribute()) + { + AddAttribute(reader.LocalName, reader.Value); + } + } + if (empty) + { + EndElement(); + } + break; + case XmlNodeType.Text: + case XmlNodeType.CDATA: + case XmlNodeType.Whitespace: + case XmlNodeType.SignificantWhitespace: + AddText(reader.Value); + break; + case XmlNodeType.EndElement: + EndElement(); + break; + } + } + } + + private XmlElement GetCurrentElement() + { + if (_elementStack.Count > 0) + { + return _elementStack[_elementStack.Count - 1]; + } + else + { + return null; + } + } + + private void AddText(String text) + { + XmlElement currentElement = GetCurrentElement(); + if (currentElement != null) + { + currentElement.AppendChild(_currentTopLevelElementDocument.CreateTextNode(text)); + } + } + + private void EndElement() + { + if (_elementStack.Count > 0) //we don't push the top-level MULTI_OBJECT_ELEMENT on the stack + { + XmlElement element = _elementStack[_elementStack.Count - 1]; + _elementStack.RemoveAt(_elementStack.Count - 1); + if (_elementStack.Count == 0) + { + _currentTopLevelElementDocument = null; + if (_stillHandling) + { + XmlObjectDecoder decoder = new XmlObjectDecoder(element, null); + Object obj = decoder.ReadObject(); + _stillHandling = _handler(obj); + } + } + } + } + + + private void StartElement(String localName) + { + XmlElement element = null; + if (_elementStack.Count == 0) + { + if (!XmlObjectSerializerImpl.MULTI_OBJECT_ELEMENT.Equals(localName)) + { + _currentTopLevelElementDocument = new XmlDocument(); + element = _currentTopLevelElementDocument.CreateElement(localName); + } + } + else + { + element = + _currentTopLevelElementDocument.CreateElement(localName); + GetCurrentElement().AppendChild(element); + } + if (element != null) + { + _elementStack.Add(element); + } + } + + private void AddAttribute(String name, String val) + { + XmlElement element = GetCurrentElement(); + if (element != null) + { + element.SetAttribute(name, val); + } + } + } + } + + public class XmlObjectSerializerImpl : XmlObjectSerializer + { + + public const String MULTI_OBJECT_ELEMENT = "MultiObject"; + public const String CONNECTORS_DTD = "connectors.dtd"; + + private readonly TextWriter _output; + + private readonly bool _multiObject; + + private readonly bool _includeHeader; + + private bool _firstObjectWritten; + + private bool _documentEnded; + + public XmlObjectSerializerImpl(TextWriter output, bool includeHeader, bool multiObject) + { + _output = output; + _includeHeader = includeHeader; + _multiObject = multiObject; + } + + + /// + /// Writes the next object to the stream. + /// + /// The object to write. + /// + /// if there is more than one object + /// and this is not configured for multi-object document. + public void WriteObject(Object obj) + { + if (_documentEnded) + { + throw new InvalidOperationException("Attempt to writeObject after the document is already closed"); + } + StringBuilder buf = new StringBuilder(); + XmlObjectEncoder encoder = new XmlObjectEncoder(buf); + String elementName = encoder.WriteObject(obj); + if (!_firstObjectWritten) + { + StartDocument(elementName); + } + else + { + if (!_multiObject) + { + throw new InvalidOperationException("Attempt to write multiple objects on a single-object document"); + } + } + Write(buf.ToString()); + _firstObjectWritten = true; + } + + public void Flush() + { + _output.Flush(); + } + + public void Close(bool closeStream) + { + if (!_documentEnded) + { + if (!_firstObjectWritten) + { + if (!_multiObject) + { + throw new InvalidOperationException("Attempt to write zero objects on a single-object document"); + } + StartDocument(null); + } + WriteEndDocument(); + _documentEnded = true; + } + if (closeStream) + { + _output.Close(); + } + } + + private void StartDocument(String firstElement) + { + if (_includeHeader) + { + String docType = _multiObject ? MULTI_OBJECT_ELEMENT : firstElement; + String line1 = "\n"; + String line2 = "\n"; + Write(line1); + Write(line2); + } + if (_multiObject) + { + String line3 = "<" + MULTI_OBJECT_ELEMENT + ">\n"; + Write(line3); + } + } + + private void WriteEndDocument() + { + if (_multiObject) + { + String line1 = "\n"; + Write(line1); + } + } + + private void Write(String str) + { + _output.Write(str); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/Server.cs b/dotnet/framework/FrameworkInternal/Server.cs new file mode 100644 index 00000000..3ec834b5 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/Server.cs @@ -0,0 +1,1154 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Net.Sockets; +using System.IO; +using System.Linq; +using System.Threading; +using System.Reflection; +using System.Security.Authentication; +using System.Text; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Server; +using Org.IdentityConnectors.Framework.Impl.Api; +using Org.IdentityConnectors.Framework.Impl.Api.Remote.Messages; +using Org.IdentityConnectors.Framework.Impl.Api.Local; +using Org.IdentityConnectors.Framework.Impl.Api.Remote; + +namespace Org.IdentityConnectors.Framework.Server +{ + /// + /// Connector server interface. + /// + public abstract class ConnectorServer + { + // At some point we might make this pluggable, but for now, hard-code + private const String IMPL_NAME + = "Org.IdentityConnectors.Framework.Impl.Server.ConnectorServerImpl"; + + protected internal static readonly TraceSource Logger = new TraceSource("ConnectorServer"); + + /// + /// The port to listen on; + /// + private int _port = 0; + + /// + /// Base 64 sha1 hash of the connector server key + /// + /// + private String _keyHash; + + /// + /// The number of connections to queue + /// + private int _maxConnections = 300; + + /// + /// The minimum number of worker threads + /// + private int _minWorkers = 10; + + /// + /// The maximum number of worker threads + /// + private int _maxWorkers = 100; + + /// + /// The maximum time in minutes a facade can be inactive. + /// + private int _maxFacadeLifeTime = 60; + + /// + /// The network interface address to use. + /// + private IPAddress _ifAddress = null; + + /// + /// Listen on SSL + /// + private bool _useSSL = false; + + /// + /// The server certificate to use + /// + private X509Certificate _serverCertificate = null; + + /// + /// Get the singleton instance of the . + /// + public static ConnectorServer NewInstance() + { + SafeType type = + SafeType.ForRawType(Type.GetType(IMPL_NAME, true)); + return type.CreateInstance(); + } + + private void AssertNotStarted() + { + if (IsStarted()) + { + throw new InvalidOperationException("Operation cannot be performed " + + "while server is running"); + } + } + + /// + /// Returns the port to listen on. + /// + /// The port to listen on. + public int Port + { + get + { + return _port; + } + set + { + AssertNotStarted(); + _port = value; + } + } + + /// + /// Returns the max connections to queue + /// + /// The max connections to queue + public int MaxConnections + { + get + { + return _maxConnections; + } + set + { + AssertNotStarted(); + _maxConnections = value; + } + } + + /// + /// Returns the max worker threads to allow. + /// + /// The max worker threads to allow. + public int MaxWorkers + { + get + { + return _maxWorkers; + } + set + { + AssertNotStarted(); + _maxWorkers = value; + } + } + + /// + /// Returns the min worker threads to allow. + /// + /// The min worker threads to allow. + public int MinWorkers + { + get + { + return _minWorkers; + } + set + { + AssertNotStarted(); + _minWorkers = value; + } + } + + /// + /// Returns the max inactive lifetime of + /// to allow. + /// + /// The max inactive lifetime of + /// to + /// allow. + public virtual int MaxFacadeLifeTime + { + get + { + return _maxFacadeLifeTime; + } + set + { + AssertNotStarted(); + _maxFacadeLifeTime = value; + } + } + + /// + /// Returns the network interface address to bind to. + /// + /// + /// May be null. + /// + /// The network interface address to bind to or null. + public IPAddress IfAddress + { + get + { + return _ifAddress; + } + set + { + AssertNotStarted(); + _ifAddress = value; + } + } + + /// + /// Returns true if we are to use SSL. + /// + /// true if we are to use SSL. + public bool UseSSL + { + get + { + return _useSSL; + } + set + { + AssertNotStarted(); + _useSSL = value; + } + } + + /// + /// Returns the certificate to use for the SSL connection. + /// + public X509Certificate ServerCertificate + { + get + { + return _serverCertificate; + } + set + { + AssertNotStarted(); + _serverCertificate = value; + } + } + + public String KeyHash + { + get + { + return _keyHash; + } + set + { + AssertNotStarted(); + _keyHash = value; + } + } + + /// + /// Produces a thread dump of all pending requests + /// + abstract public void DumpRequests(); + + /// + /// Gets the time when the servers was started last time. + /// + abstract public long StartTime(); + + /// + /// Starts the server. + /// + /// + /// All server settings must be configured prior + /// to calling. The following methods are required to be called: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + abstract public void Start(); + + /// + /// Stops the server gracefully. + /// + /// + /// Returns when all in-progress connections + /// have been serviced. + /// + abstract public void Stop(); + + /// + /// Return true if the server is started. + /// + /// + /// Note that started is a + /// logical state (start method has been called). It does not necessarily + /// reflect the health of the server + /// + /// true if the server is started. + abstract public bool IsStarted(); + } +} + +namespace Org.IdentityConnectors.Framework.Impl.Server +{ + + #region ConnectionProcessor + public class ConnectionProcessor + { + private class RemoteResultsHandler : ObjectStreamHandler + { + private const int PAUSE_INTERVAL = 200; + + private readonly RemoteFrameworkConnection _connection; + private long _count = 0; + public RemoteResultsHandler(RemoteFrameworkConnection conn) + { + _connection = conn; + } + + public bool Handle(Object obj) + { + try + { + OperationResponsePart part = + new OperationResponsePart(null, obj); + _connection.WriteObject(part); + _count++; + if (_count % PAUSE_INTERVAL == 0) + { + _connection.WriteObject(new OperationResponsePause()); + Object message = + _connection.ReadObject(); + return message is OperationRequestMoreData; + } + else + { + return true; + } + } + catch (IOException e) + { + throw new BrokenConnectionException(e); + } + } + } + + private readonly ConnectorServerImpl _server; + private readonly RemoteFrameworkConnection _connection; + + public ConnectionProcessor(ConnectorServerImpl server, + RemoteFrameworkConnection connection) + { + _server = server; + _connection = connection; + } + + public void Run() + { + try + { + _server.BeginRequest(); + try + { + while (true) + { + bool keepGoing = ProcessRequest(); + if (!keepGoing) + { + break; + } + } + } + finally + { + try + { + _connection.Dispose(); + } + catch (Exception e) + { + TraceUtil.TraceException(null, e); + } + } + } + catch (Exception e) + { + TraceUtil.TraceException(null, e); + } + finally + { + _server.EndRequest(); + } + } + + private bool ProcessRequest() + { + CultureInfo locale; + try + { + locale = (CultureInfo)_connection.ReadObject(); + } + catch (EndOfStreamException) + { + return false; + } + + //We can't set this because C# does not like language-neutral + //cultures for CurrentCulture - this tends to blow up + //TODO: think more about this... + //Thread.CurrentThread.CurrentCulture = locale; + Thread.CurrentThread.CurrentUICulture = locale; + + GuardedString key = (GuardedString)_connection.ReadObject(); + + bool authorized; + try + { + authorized = key.VerifyBase64SHA1Hash(_server.KeyHash); + } + finally + { + key.Dispose(); + } + Org.IdentityConnectors.Framework.Common.Exceptions.InvalidCredentialException authException = null; + if (!authorized) + { + authException = new Org.IdentityConnectors.Framework.Common.Exceptions.InvalidCredentialException("Remote framework key is invalid"); + } + Object requestObject = _connection.ReadObject(); + if (requestObject is HelloRequest) + { + if (authException != null) + { + HelloResponse response = + new HelloResponse(authException, null, null, null); + _connection.WriteObject(response); + } + else + { + HelloResponse response = + ProcessHelloRequest((HelloRequest)requestObject); + _connection.WriteObject(response); + } + } + else if (requestObject is OperationRequest) + { + if (authException != null) + { + OperationResponsePart part = + new OperationResponsePart(authException, null); + _connection.WriteObject(part); + } + else + { + OperationRequest opRequest = + (OperationRequest)requestObject; + OperationResponsePart part = + ProcessOperationRequest(opRequest); + _connection.WriteObject(part); + } + } + else if (requestObject is EchoMessage) + { + if (authException != null) + { + //echo message probably doesn't need auth, but + //it couldn't hurt - actually it does for test connection + EchoMessage part = + new EchoMessage(authException, null); + _connection.WriteObject(part); + } + else + { + EchoMessage message = (EchoMessage)requestObject; + Object obj = message.Object; + String xml = message.ObjectXml; + if (xml != null) + { + Console.WriteLine("xml: \n" + xml); + Object xmlClone = + SerializerUtil.DeserializeXmlObject(xml, true); + xml = + SerializerUtil.SerializeXmlObject(xmlClone, true); + } + EchoMessage message2 = new EchoMessage(obj, xml); + _connection.WriteObject(message2); + } + } + else + { + throw new Exception("Unexpected request: " + requestObject); + } + return true; + } + + private ConnectorInfoManager GetConnectorInfoManager() + { + return ConnectorInfoManagerFactory.GetInstance().GetLocalManager(); + } + + private HelloResponse ProcessHelloRequest(HelloRequest request) + { + IList connectorInfo = null; + IList connectorKeys = null; + IDictionary serverInfo = null; + Exception exception = null; + try + { + serverInfo = new Dictionary(1); + if (request.isServerInfo()) + { + serverInfo.Add(HelloResponse.SERVER_START_TIME, _server.StartTime()); + } + if (request.isConnectorKeys()) + { + ConnectorInfoManager manager = GetConnectorInfoManager(); + IList localInfos = manager.ConnectorInfos; + connectorKeys = new List(); + foreach (ConnectorInfo localInfo in localInfos) + { + connectorKeys.Add(localInfo.ConnectorKey); + } + if (request.isConnectorInfo()) + { + connectorInfo = new List(); + foreach (ConnectorInfo localInfo in localInfos) + { + LocalConnectorInfoImpl localInfoImpl = + (LocalConnectorInfoImpl)localInfo; + RemoteConnectorInfoImpl remoteInfo = + localInfoImpl.ToRemote(); + connectorInfo.Add(remoteInfo); + } + } + } + } + catch (Exception e) + { + TraceUtil.TraceException(null, e); + exception = e; + connectorInfo = null; + } + return new HelloResponse(exception, serverInfo, connectorKeys, connectorInfo); + } + + private MethodInfo GetOperationMethod(OperationRequest request) + { + MethodInfo[] methods = + request.Operation.RawType.GetMethods(); + MethodInfo found = null; + foreach (MethodInfo m in methods) + { + if (m.Name.ToUpper().Equals(request.OperationMethodName.ToUpper())) + { + if (found != null) + { + throw new ConnectorException("APIOperations are expected " + + "to have exactly one method of a given name: " + request.Operation); + } + found = m; + } + } + + if (found == null) + { + throw new ConnectorException("APIOperations are expected " + + "to have exactly one method of a given name: " + request.Operation + " " + methods.Length); + } + return found; + } + + private OperationResponsePart + ProcessOperationRequest(OperationRequest request) + { + Object result; + Exception exception = null; + try + { + MethodInfo method = GetOperationMethod(request); + APIOperation operation = GetAPIOperation(request); + IList arguments = request.Arguments; + IList argumentsAndStreamHandlers = + PopulateStreamHandlers(ReflectionUtil.GetParameterTypes(method), + arguments); + try + { + Object[] args = argumentsAndStreamHandlers.ToArray(); + FixupArguments(method, args); + result = method.Invoke(operation, args); + } + catch (TargetInvocationException e) + { + Exception root = e.InnerException; + ExceptionUtil.PreserveStackTrace(root); + throw root; + } + bool anyStreams = + argumentsAndStreamHandlers.Count > arguments.Count; + if (anyStreams) + { + try + { + _connection.WriteObject(new OperationResponseEnd()); + } + catch (IOException e) + { + throw new BrokenConnectionException(e); + } + } + } + catch (BrokenConnectionException w) + { + //at this point the stream is broken - just give up + throw w.GetIOException(); + } + catch (Exception e) + { + TraceUtil.TraceException(null, e); + exception = e; + result = null; + } + return new OperationResponsePart(exception, result); + } + + private IList PopulateStreamHandlers(Type[] paramTypes, IList arguments) + { + IList rv = new List(); + bool firstStream = true; + IEnumerator argIt = arguments.GetEnumerator(); + foreach (Type paramType in paramTypes) + { + if (StreamHandlerUtil.IsAdaptableToObjectStreamHandler(paramType)) + { + if (!firstStream) + { + throw new InvalidOperationException("At most one stream handler is supported"); + } + ObjectStreamHandler osh = + new RemoteResultsHandler(_connection); + rv.Add(StreamHandlerUtil.AdaptFromObjectStreamHandler(paramType, osh)); + firstStream = false; + } + else + { + argIt.MoveNext(); + rv.Add(argIt.Current); + } + } + return rv; + } + + /// + /// When arguments are serialized, we loose the + /// generic-type of collections. We must fix + /// the arguments + /// + /// + /// + private void FixupArguments(MethodInfo method, + object[] args) + { + Type[] paramTypes = + ReflectionUtil.GetParameterTypes(method); + if (paramTypes.Length != args.Length) + { + throw new ArgumentException("Number of arguments does not match for method: " + method); + } + for (int i = 0; i < args.Length; i++) + { + args[i] = FixupArgument(paramTypes[i], + args[i]); + } + } + + private object FixupArgument(Type expectedType, + object argument) + { + //at some point, we might want this to be more general-purpose + //for now we just handle those cases that we need to + if (typeof(ICollection).Equals(expectedType)) + { + ICollection val = + (ICollection)argument; + return CollectionUtil.NewSet(val); + } + else + { + return argument; + } + } + + private APIOperation GetAPIOperation(OperationRequest request) + { + ConnectorInfoManager manager = + GetConnectorInfoManager(); + ConnectorInfo info = manager.FindConnectorInfo( + request.ConnectorKey); + if (info == null) + { + throw new Exception("No such connector: " + + request.ConnectorKey); + } + String connectorFacadeKey = request.ConnectorFacadeKey; + + ConnectorFacade facade = + ConnectorFacadeFactory.GetManagedInstance().NewInstance(info, connectorFacadeKey); + + return facade.GetOperation(request.Operation); + } + + private class BrokenConnectionException : Exception + { + + + public BrokenConnectionException(IOException ex) + : base("", ex) + { + } + + public IOException GetIOException() + { + return (IOException)InnerException; + } + } + + } + #endregion + + #region ConnectionListener + class ConnectionListener + { + /// + /// This is the size of our internal queue. + /// + /// + /// For now I have this + /// relatively small because I want the OS to manage the connect + /// queue coming in. That way it can properly turn away excessive + /// requests + /// + private const int INTERNAL_QUEUE_SIZE = 2; + + + /// + /// The server object that we are using + /// + private readonly ConnectorServerImpl _server; + + /// + /// The server socket. + /// + /// + /// This must be bound at the time + /// of creation. + /// + private readonly TcpListener _socket; + + /// + /// Pool of executors + /// + //TODO: add a thread pool + //private readonly ExecutorService _threadPool; + + /// + /// Set to indicated we need to start shutting down + /// + private bool _stopped = false; + + private Thread _thisThread; + + private readonly Object MUTEX = new Object(); + + /// + /// Creates the listener thread + /// + /// The server object + /// The socket (should already be bound) + public ConnectionListener(ConnectorServerImpl server, + TcpListener socket) + { + _server = server; + _socket = socket; + _thisThread = new Thread(Run) { Name = "ConnectionListener", IsBackground = false }; + //TODO: thread pool + /* _threadPool = + new ThreadPoolExecutor + (server.getMinWorkers(), + server.getMaxWorkers(), + 30, //idle time timeout + TimeUnit.SECONDS, + new ArrayBlockingQueue( + INTERNAL_QUEUE_SIZE, + true)); //fair*/ + } + + public void Start() + { + _thisThread.Start(); + } + + public void Run() + { + Trace.TraceInformation("Server started on port: " + _server.Port); + while (!IsStopped()) + { + try + { + TcpClient connection = null; + Stream stream = null; + try + { + connection = _socket.AcceptTcpClient(); + stream = connection.GetStream(); + if (_server.UseSSL) + { + SslStream sslStream = new SslStream(stream, false); + stream = sslStream; + sslStream.AuthenticateAsServer(_server.ServerCertificate, + false, + SslProtocols.Tls, + false); + } + + ConnectionProcessor processor = + new ConnectionProcessor(_server, + new RemoteFrameworkConnection(connection, stream)); + Thread thread = new Thread(processor.Run); + thread.IsBackground = false; + thread.Start(); + } + catch (Exception) + { + if (stream != null) + { + try { stream.Close(); } + catch (Exception) { } + } + if (connection != null) + { + try { connection.Close(); } + catch (Exception) { } + } + throw; + } + } + catch (Exception e) + { + //log the error unless it's because we've stopped + if (!IsStopped() || !(e is SocketException)) + { + TraceUtil.TraceException("Error processing request", e); + } + //wait a second before trying again + if (!IsStopped()) + { + Thread.Sleep(1000); + } + } + } + } + + private void MarkStopped() + { + lock (MUTEX) + { + _stopped = true; + } + } + + private bool IsStopped() + { + lock (MUTEX) + { + return _stopped; + } + } + + public void Shutdown() + { + if (Object.ReferenceEquals(Thread.CurrentThread, _thisThread)) + { + throw new ArgumentException("Shutdown may not be called from this thread"); + } + if (!IsStopped()) + { + //set the stopped flag so we no its a normal + //shutdown and don't log the SocketException + MarkStopped(); + //close the socket - this causes accept to throw an exception + _socket.Stop(); + //wait for the main listener thread to die so we don't + //get any new requests + _thisThread.Join(); + //TODO: shutdown thread pool + //wait for all in-progress requests to finish + //_threadPool.shutdown(); + } + } + } + #endregion + + #region RequestStats + internal class RequestStats + { + public RequestStats() + { + } + public Thread RequestThread { get; set; } + public long StartTimeMillis { get; set; } + public long RequestID { get; set; } + } + #endregion + + #region ConnectorServerImpl + public class ConnectorServerImpl : ConnectorServer + { + + private readonly IDictionary + _pendingRequests = CollectionUtil.NewIdentityDictionary(); + private ConnectionListener _listener; + private Object COUNT_LOCK = new Object(); + private Timer _timer = null; + private long _startDate = 0; + private long _requestCount = 0; + + public override bool IsStarted() + { + return _listener != null; + } + + public override long StartTime() + { + return _startDate; + } + + public void BeginRequest() + { + long requestID; + lock (COUNT_LOCK) + { + requestID = _requestCount++; + } + Thread requestThread = Thread.CurrentThread; + RequestStats stats = new RequestStats(); + stats.StartTimeMillis = + DateTimeUtil.GetCurrentUtcTimeMillis(); + stats.RequestThread = Thread.CurrentThread; + stats.RequestID = requestID; + lock (_pendingRequests) + { + _pendingRequests[stats.RequestThread] + = stats; + } + } + + public void EndRequest() + { + lock (_pendingRequests) + { + _pendingRequests.Remove(Thread.CurrentThread); + } + } + public override void DumpRequests() + { + long currentTime = DateTimeUtil.GetCurrentUtcTimeMillis(); + IDictionary + pending; + lock (_pendingRequests) + { + pending = new Dictionary(_pendingRequests); + } + StringBuilder builder = new StringBuilder(); + builder.Append("****Pending Requests Summary*****"); + foreach (RequestStats stats in pending.Values) + { + DumpStats(stats, builder, currentTime); + } + //here we purposefully use write line since + //we always want to see it. in general, don't + //use this method + Trace.WriteLine(builder.ToString()); + } + + private void DumpStats(RequestStats stats, + StringBuilder builder, + long currentTime) + { + builder.AppendLine("**Request #" + stats.RequestID + " pending for " + (currentTime - stats.StartTimeMillis) + " millis."); + StackTrace stackTrace = GetStackTrace(stats.RequestThread); + if (stackTrace == null) + { + builder.AppendLine(" "); + } + else + { + builder.AppendLine(stackTrace.ToString()); + } + } + + private static StackTrace GetStackTrace(Thread thread) + { + bool suspended = false; + try + { + thread.Suspend(); + suspended = true; + return new StackTrace(thread, true); + } + catch (ThreadStateException) + { + return null; //we missed this one + } + finally + { + if (suspended) + { + thread.Resume(); + } + } + } + + public override void Start() + { + if (IsStarted()) + { + throw new InvalidOperationException("Server is already running."); + } + if (Port == 0) + { + throw new InvalidOperationException("Port must be set prior to starting server."); + } + if (KeyHash == null) + { + throw new InvalidOperationException("Key hash must be set prior to starting server."); + } + if (UseSSL && ServerCertificate == null) + { + throw new InvalidOperationException("ServerCertificate must be set if using SSL."); + } + //make sure we are configured properly + ConnectorInfoManagerFactory.GetInstance().GetLocalManager(); + _requestCount = 0; + /* + * the Java and .Net dates have a different starting point: zero milliseconds in Java corresponds to January 1, 1970, 00:00:00 GMT (aka “the epoch”). + * In .Net zero milliseconds* corresponds to 12:00 A.M., January 1, 0001 GMT. + * So the basic is to bridge over the reference points gap with adding (or substracting) the corresponding number of milliseconds + * such that zero milliseconds in .Net is mapped to -62135769600000L milliseconds in Java. + * This number of milliseconds corresponds to GMT zone, so do not forget to include your time zone offset into the calculations. + */ + _startDate = (DateTime.UtcNow.Ticks - 621355968000000000) / 10000; + _pendingRequests.Clear(); + TcpListener socket = + CreateServerSocket(); + ConnectionListener listener = new ConnectionListener(this, socket); + listener.Start(); + _listener = listener; + + if (MaxFacadeLifeTime > 0) + { + var statusChecker = new FacadeDisposer(new TimeSpan(0, MaxFacadeLifeTime, 0)); + // Create an inferred delegate that invokes methods for the timer. + TimerCallback tcb = statusChecker.Run; + + _timer = new Timer(tcb, null, new TimeSpan(0, MaxFacadeLifeTime, 0), + new TimeSpan(0, Math.Min(MaxFacadeLifeTime, 10), 0)); + } + } + + private TcpListener CreateServerSocket() + { + IPAddress addr = IfAddress; + + if (addr == null) + { + addr = IOUtil.GetIPAddress("0.0.0.0"); + } + TcpListener rv = new TcpListener(addr, Port); + //TODO: specify accept count + rv.Start(); + return rv; + } + + + public override void Stop() + { + if (_listener != null) + { + _listener.Shutdown(); + _listener = null; + } + _startDate = 0; + if (null != _timer) + { + _timer.Dispose(); + _timer = null; + } + ConnectorFacadeFactory.GetManagedInstance().Dispose(); + } + + internal class FacadeDisposer + { + private readonly TimeSpan _delay; + private int _sequence = 0; + + public FacadeDisposer(TimeSpan unit) + { + this._delay = unit; + } + + public void Run(Object stateInfo) + { + Logger.TraceEvent(TraceEventType.Verbose, _sequence++, + "Invoking Managed ConnectorFacade Disposer : {0:yyyy/MM/dd H:mm:ss zzz}", DateTime.Now); + ConnectorFacadeFactory factory = ConnectorFacadeFactory.GetManagedInstance(); + if (factory is ManagedConnectorFacadeFactoryImpl) + { + ((ManagedConnectorFacadeFactoryImpl)factory).EvictIdle(_delay); + } + } + } + } + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/Test.cs b/dotnet/framework/FrameworkInternal/Test.cs new file mode 100644 index 00000000..12a68601 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/Test.cs @@ -0,0 +1,242 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014-2015 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text; + +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Impl.Api; +using Org.IdentityConnectors.Framework.Impl.Api.Local; +using Org.IdentityConnectors.Framework.Impl.Api.Local.Operations; +using Org.IdentityConnectors.Framework.Common; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; +using Org.IdentityConnectors.Test.Common; +using Org.IdentityConnectors.Test.Common.Spi; + +namespace Org.IdentityConnectors.Framework.Impl.Test +{ + public class TestHelpersImpl : TestHelpersSpi + { + /// + /// Method for convenient testing of local connectors. + /// + public APIConfiguration CreateTestConfiguration(SafeType clazz, + Configuration config) + { + LocalConnectorInfoImpl info = new LocalConnectorInfoImpl(); + info.ConnectorConfigurationClass = SafeType.Get(config); + info.ConnectorClass = (clazz); + info.ConnectorDisplayNameKey = ("DUMMY_DISPLAY_NAME"); + info.ConnectorKey = ( + new ConnectorKey(clazz.RawType.Name + ".bundle", + "1.0", + clazz.RawType.Name)); + info.Messages = (this.CreateDummyMessages()); + APIConfigurationImpl rv = new APIConfigurationImpl(); + rv.IsConnectorPoolingSupported = ( + IsConnectorPoolingSupported(clazz)); + ConfigurationPropertiesImpl properties = + CSharpClassProperties.CreateConfigurationProperties(config); + rv.ConfigurationProperties = (properties); + rv.ConnectorInfo = (info); + rv.SupportedOperations = ( + FrameworkUtil.GetDefaultSupportedOperations(clazz)); + info.DefaultAPIConfiguration = ( + rv); + return rv; + } + + /// + /// Method for convenient testing of local connectors. + /// + public APIConfiguration CreateTestConfiguration(SafeType connectorClass, PropertyBag configData, string prefix) + { + Debug.Assert(null != connectorClass); + Type rawConnectorClass = connectorClass.RawType; + + Object[] attributes = connectorClass.RawType.GetCustomAttributes( + typeof(ConnectorClassAttribute), + false); + if (attributes.Length > 0) + { + ConnectorClassAttribute attribute = + (ConnectorClassAttribute)attributes[0]; + + Assembly assembly = IOUtil.GetAssemblyContainingType(rawConnectorClass.FullName); + + String fileName = assembly.Location; + SafeType connectorConfigurationClass = attribute.ConnectorConfigurationType; + if (connectorConfigurationClass == null) + { + String MSG = ("File " + fileName + + " contains a ConnectorInfo attribute " + + "with no connector configuration class."); + throw new ConfigurationException(MSG); + } + String connectorDisplayNameKey = + attribute.ConnectorDisplayNameKey; + if (connectorDisplayNameKey == null) + { + String MSG = ("File " + fileName + + " contains a ConnectorInfo attribute " + + "with no connector display name."); + throw new ConfigurationException(MSG); + } + LocalConnectorInfoImpl rv = new LocalConnectorInfoImpl(); + rv.ConnectorClass = connectorClass; + rv.ConnectorConfigurationClass = connectorConfigurationClass; + rv.ConnectorDisplayNameKey = connectorDisplayNameKey; + rv.ConnectorCategoryKey = attribute.ConnectorCategoryKey; + rv.ConnectorKey = ( + new ConnectorKey(rawConnectorClass.Name + ".bundle", + "1.0", + rawConnectorClass.Name)); ; + APIConfigurationImpl impl = ConnectorAssemblyUtility.CreateDefaultApiConfiguration(rv); + rv.DefaultAPIConfiguration = impl; + rv.Messages = ConnectorAssemblyUtility.LoadMessages(assembly, rv, attribute.MessageCatalogPaths); + + ConfigurationPropertiesImpl configProps = (ConfigurationPropertiesImpl)impl.ConfigurationProperties; + + string fullPrefix = StringUtil.IsBlank(prefix) ? null : prefix + "."; + + foreach (ConfigurationPropertyImpl property in configProps.Properties) + { + object value = configData.GetProperty(null != fullPrefix ? fullPrefix + property.Name : property.Name, property.ValueType, property.Value); + if (value != null) + { + property.Value = value; + } + } + return impl; + } + throw new ArgumentException("ConnectorClass does not define ConnectorClassAttribute"); + } + + public void FillConfiguration(Configuration config, IDictionary configData) + { + IDictionary configDataCopy = new Dictionary(configData); + ConfigurationPropertiesImpl configProps = + CSharpClassProperties.CreateConfigurationProperties(config); + foreach (string propName in configProps.PropertyNames) + { + object value; + if (configDataCopy.TryGetValue(propName, out value)) + { + // Remove the entry from the config map, so that at the end + // the map only contains entries that were not assigned to a config property. + configDataCopy.Remove(propName); + configProps.SetPropertyValue(propName, value); + } + } + // The config map now contains entries that were not assigned to a config property. + foreach (string propName in configDataCopy.Keys) + { + Trace.TraceWarning("Configuration property {0} does not exist!", propName); + } + CSharpClassProperties.MergeIntoBean(configProps, config); + } + + private static bool IsConnectorPoolingSupported(SafeType clazz) + { + return ReflectionUtil.IsParentTypeOf(typeof(PoolableConnector), clazz.RawType); + } + + /// + /// Performs a raw, unfiltered search at the SPI level, + /// eliminating duplicates from the result set. + /// + /// The search SPI + /// The object class - passed through to + /// connector so it may be null if the connecor + /// allowing it to be null. (This is convenient for + /// unit tests, but will not be the case in general) + /// The filter to search on + /// The result handler + /// The options - may be null - will + /// be cast to an empty OperationOptions + public SearchResult Search(SearchOp search, + ObjectClass objectClass, + Filter filter, + ResultsHandler handler, + OperationOptions options) where T : class + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new System.NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(handler, "handler"); + //convert null into empty + if (options == null) + { + options = new OperationOptionsBuilder().Build(); + } + + SearchResult result = null; + RawSearcherImpl.RawSearch(search, objectClass, filter, new SearchResultsHandler() + { + Handle = obj => + { + return handler.Handle(obj); + }, + HandleResult = obj => + { + result = obj; + } + + }, options); + return result != null ? result : new SearchResult(); + } + + public ConnectorMessages CreateDummyMessages() + { + return new DummyConnectorMessages(); + } + + private class DummyConnectorMessages : ConnectorMessages + { + public String Format(String key, String dflt, params Object[] args) + { + StringBuilder builder = new StringBuilder(); + builder.Append(key); + builder.Append(": "); + String sep = ""; + foreach (Object arg in args) + { + builder.Append(sep); + builder.Append(arg); + sep = ", "; + } + return builder.ToString(); + } + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkInternal/version.template b/dotnet/framework/FrameworkInternal/version.template new file mode 100644 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/FrameworkInternal/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/FrameworkProtoBuf/FrameworkProtoBuf.csproj b/dotnet/framework/FrameworkProtoBuf/FrameworkProtoBuf.csproj new file mode 100755 index 00000000..ac5f4bbe --- /dev/null +++ b/dotnet/framework/FrameworkProtoBuf/FrameworkProtoBuf.csproj @@ -0,0 +1,82 @@ + + + + + + Debug + AnyCPU + {5A9E8C5B-4D41-4E3E-9680-6C195BFAD47A} + Library + Properties + Org.ForgeRock.OpenICF.Common.ProtoBuf + FrameworkProtoBuf + OpenICF Framework - Protocol Buffer Messages + v4.5.2 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + ..\packages\Google.ProtocolBuffers.3\lib\Google.Protobuf.dll + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkProtoBuf/protobuf/CommonObjectMessages.proto b/dotnet/framework/FrameworkProtoBuf/protobuf/CommonObjectMessages.proto new file mode 100755 index 00000000..89888f01 --- /dev/null +++ b/dotnet/framework/FrameworkProtoBuf/protobuf/CommonObjectMessages.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; + +option csharp_namespace = "Org.ForgeRock.OpenICF.Common.ProtoBuf"; +package org.forgerock.openicf.common.protobuf; + +message Uid { + string value = 1; + string revision = 2; +} + +message BigDecimal { + string unscaled = 1; + int32 scale = 2; +} + +message ConnectorKey { + string bundleName = 1; + string bundleVersion = 2; + string connectorName = 3; +} + +message Locale { + string language = 1; + string country = 2; + string variant = 3; +} + +message Script { + string scriptLanguage = 1; + string scriptText = 2; +} + +message ScriptContext { + Script script = 1; + bytes scriptArguments = 2; +} + +message SearchResult { + enum CountPolicy { + NONE = 0; + ESTIMATE = 1; + EXACT = 2; + } + string pagedResultsCookie = 1; + CountPolicy totalPagedResultsPolicy = 2; + int32 totalPagedResults = 3; + int32 remainingPagedResults = 4; +} + +message SortKey { + string field = 1; + bool isAscending = 2; +} + +message SyncToken { + bytes value = 1; +} + +message SyncDelta { + enum SyncDeltaType { + CREATE_OR_UPDATE = 0; + DELETE = 1; + CREATE = 2; + UPDATE = 3; + } + SyncToken token = 1; + SyncDeltaType deltaType = 2; + Uid previousUid = 3; + string objectClass = 4; + Uid uid = 5; + bytes connectorObject = 6; + +} + +message ConnectorObject { + /*message MapFieldEntry { + //option map_entry = true; + string key = 1; + AttributeCollectionValue value = 2; + }*/ + string objectClass = 1; + bytes attributes = 2; + //repeated MapFieldEntry map_field = 2; +} + +message QualifiedUid { + string objectClass = 1; + Uid uid = 2; +} diff --git a/dotnet/framework/FrameworkProtoBuf/protobuf/OperationMessages.proto b/dotnet/framework/FrameworkProtoBuf/protobuf/OperationMessages.proto new file mode 100755 index 00000000..2b53b490 --- /dev/null +++ b/dotnet/framework/FrameworkProtoBuf/protobuf/OperationMessages.proto @@ -0,0 +1,277 @@ +syntax = "proto3"; + +option csharp_namespace = "Org.ForgeRock.OpenICF.Common.ProtoBuf"; +package org.forgerock.openicf.common.protobuf; + +import "CommonObjectMessages.proto"; + +//import "FilterMessages.proto"; + +//Interface AuthenticateOp +message AuthenticateOpRequest { + string objectClass = 1; + string username = 2; + bytes password = 3; + bytes options = 4; +} + +message AuthenticateOpResponse { + Uid uid = 1; +} + +//Interface BatchOp +message BatchOpRequest { + bool query = 1; + repeated BatchOpTask tasks = 2; + repeated string batchToken = 3; + bytes options = 4; +} + +message BatchOpTask { + string taskId = 1; + CreateOpRequest createRequest = 2; + DeleteOpRequest deleteRequest = 3; + UpdateOpRequest updateRequest = 4; +} + +message BatchOpResult { + repeated string batchToken = 1; + string taskId = 2; + bool complete = 3; + bool error = 4; + bool queryRequired = 5; + bool asynchronousResults = 6; + bool returnsResults = 7; + Uid uid = 8; + BatchEmptyResponse emptyResult = 9; +} + +message BatchEmptyResponse { + string resultMessage = 2; +} + +//Interface CreateOp +message CreateOpRequest { + string objectClass = 1; + bytes createAttributes = 2; + //repeated AttributeMessage createAttributes = 2; + bytes options = 3; +} + +message CreateOpResponse { + Uid uid = 1; +} + +//Interface ConnectorEventSubscriptionOp +message ConnectorEventSubscriptionOpRequest { + string objectClass = 1; + bytes eventFilter = 2; + bytes options = 3; +} + +message ConnectorEventSubscriptionOpResponse { + ConnectorObject connectorObject = 1; + bool completed = 2; +} + +//Interface DeleteOp +message DeleteOpRequest { + string objectClass = 1; + Uid uid = 2; + bytes options = 3; +} + +message DeleteOpResponse { +} + +//Interface GetOp +message GetOpRequest { + string objectClass = 1; + Uid uid = 2; + bytes options = 3; +} + +message GetOpResponse { + bytes connectorObject = 1; +} + +//Interface ResolveUsernameOp +message ResolveUsernameOpRequest { + string objectClass = 1; + string username = 2; + bytes options = 3; +} + +message ResolveUsernameOpResponse { + Uid uid = 1; +} + +//Interface SchemaOp +message SchemaOpRequest { +} + +message SchemaOpResponse { + bytes schema = 1; +} + +//Interface ScriptOnConnectorOp +message ScriptOnConnectorOpRequest { + ScriptContext scriptContext = 1; + bytes options = 2; +} + +message ScriptOnConnectorOpResponse { + bytes object = 1; +} + +//Interface ScriptOnResourceOp +message ScriptOnResourceOpRequest { + ScriptContext scriptContext = 1; + bytes options = 2; +} + +message ScriptOnResourceOpResponse { + bytes object = 1; +} + +//Interface SearchOp +message SearchOpRequest { + string objectClass = 1; + bytes filter = 2; + //FilterUnionMessage filter = 2; + bytes options = 3; +} + +message SearchOpResponse { + int64 sequence = 1; + SearchResult result = 2; + ConnectorObject connectorObject = 3; +} + +//Interface SyncOp +message SyncOpRequest { + message Sync { + string objectClass = 1; + SyncToken token = 2; + bytes options = 3; + } + message LatestSyncToken { + string objectClass = 1; + } + Sync sync = 1; + LatestSyncToken latestSyncToken = 2; +} + +message SyncOpResponse { + message Sync { + int64 sequence = 1; + SyncToken syncToken = 2; + SyncDelta syncDelta = 3; + } + message LatestSyncToken { + SyncToken syncToken = 1; + } + Sync sync = 1; + LatestSyncToken latestSyncToken = 2; +} + +//Interface SyncEventSubscriptionOp +message SyncEventSubscriptionOpRequest { + string objectClass = 1; + SyncToken token = 2; + bytes options = 3; +} + +message SyncEventSubscriptionOpResponse { + SyncDelta syncDelta = 1; + bool completed = 2; +} + +//Interface TestOp +message TestOpRequest { +} + +message TestOpResponse { +} + +//Interface UpdateOp +message UpdateOpRequest { + enum UpdateType { + REPLACE = 0; + ADD = 1; + REMOVE = 2; + } + string objectClass = 1; + Uid uid = 2; + UpdateType updateType = 3; + bytes replaceAttributes = 4; + //repeated AttributeMessage replaceAttributes = 4; + bytes options = 5; +} + +message UpdateOpResponse { + Uid uid = 1; +} + +//Interface ValidateApiOp +message ValidateOpRequest { +} + +message ValidateOpResponse { +} + +//Configuration Update Event +message ConfigurationChangeEvent { + bytes configurationPropertyChange = 1; +} + +//_______ + + + +message OperationRequest { + ConnectorKey connectorKey = 1; + bytes connectorFacadeKey = 2; + Locale locale = 3; + + //oneof request { + AuthenticateOpRequest authenticateOpRequest = 4; + CreateOpRequest createOpRequest = 5; + ConnectorEventSubscriptionOpRequest connectorEventSubscriptionOpRequest = 6; + DeleteOpRequest deleteOpRequest = 7; + GetOpRequest getOpRequest = 8; + ResolveUsernameOpRequest resolveUsernameOpRequest = 9; + SchemaOpRequest schemaOpRequest = 10; + ScriptOnConnectorOpRequest scriptOnConnectorOpRequest = 11; + ScriptOnResourceOpRequest scriptOnResourceOpRequest = 12; + SearchOpRequest searchOpRequest = 13; + SyncOpRequest syncOpRequest = 14; + SyncEventSubscriptionOpRequest syncEventSubscriptionOpRequest = 15; + TestOpRequest testOpRequest = 16; + UpdateOpRequest updateOpRequest = 17; + ValidateOpRequest ValidateOpRequest = 18; + ConfigurationChangeEvent configurationChangeEvent = 19; + BatchOpRequest batchOpRequest = 20; + //} +} + +message OperationResponse { + //oneof response { + AuthenticateOpResponse authenticateOpResponse = 1; + CreateOpResponse createOpResponse = 2; + ConnectorEventSubscriptionOpResponse connectorEventSubscriptionOpResponse = 3; + DeleteOpResponse deleteOpResponse = 4; + GetOpResponse GetOpResponse = 5; + ResolveUsernameOpResponse resolveUsernameOpResponse = 6; + SchemaOpResponse schemaOpResponse = 7; + ScriptOnConnectorOpResponse scriptOnConnectorOpResponse = 8; + ScriptOnResourceOpResponse scriptOnResourceOpResponse = 9; + SearchOpResponse searchOpResponse = 10; + SyncOpResponse syncOpResponse = 11; + SyncEventSubscriptionOpResponse syncEventSubscriptionOpResponse = 12; + TestOpResponse testOpResponse = 13; + UpdateOpResponse updateOpResponse = 14; + ValidateOpResponse validateOpResponse = 15; + BatchOpResult batchOpResult = 16; + //} +} diff --git a/dotnet/framework/FrameworkProtoBuf/protobuf/RPCMessages.proto b/dotnet/framework/FrameworkProtoBuf/protobuf/RPCMessages.proto new file mode 100755 index 00000000..540717fe --- /dev/null +++ b/dotnet/framework/FrameworkProtoBuf/protobuf/RPCMessages.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +option csharp_namespace = "Org.ForgeRock.OpenICF.Common.ProtoBuf"; +package org.forgerock.openicf.common.protobuf; + +import "OperationMessages.proto"; + +//Handshake Message +message HandshakeMessage { + enum ServerType { + UNKNOWN = 0; + JAVA = 1; + DOTNET = 2; + } + string sessionId = 1; + ServerType serverType = 2; + bytes publicKey = 3; +} + +message ExceptionMessage { + message InnerCause { + string exceptionClass = 1; + string message = 2; + InnerCause cause = 3; + } + string exceptionClass = 1; + string message = 2; + InnerCause innerCause = 3; + string stackTrace = 4; +} + +message CancelOpRequest { + +} + +message ControlRequest { + enum InfoLevel { + CONNECTOR_INFO = 0; + CONTEXT_INFO = 1; + REQUESTS = 2; + } + repeated InfoLevel infoLevel = 1; + repeated int64 localRequestId = 2; + repeated int64 remoteRequestId = 3; +} + +message ControlResponse { + message StringMapFieldEntry { + //option map_entry = true; + string key = 1; + string value = 2; + } + repeated StringMapFieldEntry contextInfoMap = 1; + bytes connectorInfos = 2; + repeated int64 requestIds = 3; +} + +//Maximum 125 byte +message PingMessage { + +} + +message RPCRequest { + //oneof request { + HandshakeMessage handshakeMessage = 1; + ControlRequest controlRequest = 2; + OperationRequest operationRequest = 3; + CancelOpRequest cancelOpRequest = 4; + //} +} + +message RPCResponse { + //oneof response { + HandshakeMessage handshakeMessage = 1; + ControlResponse controlResponse = 2; + OperationResponse operationResponse = 3; + ExceptionMessage error = 4; + //} +} + +message RemoteMessage { + int64 messageId = 1; + //oneof message { + RPCRequest request = 2; + RPCResponse response = 3; + //} +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkProtoBuf/version.template b/dotnet/framework/FrameworkProtoBuf/version.template new file mode 100755 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/FrameworkProtoBuf/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/FrameworkRPC/FrameworkRpc.csproj b/dotnet/framework/FrameworkRPC/FrameworkRpc.csproj new file mode 100755 index 00000000..33e44431 --- /dev/null +++ b/dotnet/framework/FrameworkRPC/FrameworkRpc.csproj @@ -0,0 +1,69 @@ + + + + + + Debug + AnyCPU + {B85C5A35-E3A2-4B04-9693-795E57D66DE2} + Library + Properties + Org.ForgeRock.OpenICF.Common.Rpc + FrameworkRpc + OpenICF Framework - Remote Procedure Call API + v4.5.2 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkRPC/Rpc.cs b/dotnet/framework/FrameworkRPC/Rpc.cs new file mode 100755 index 00000000..f6a786b4 --- /dev/null +++ b/dotnet/framework/FrameworkRPC/Rpc.cs @@ -0,0 +1,1105 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Org.ForgeRock.OpenICF.Common.RPC +{ + + #region AbstractLoadBalancingAlgorithm + + /// + /// A AbstractLoadBalancingAlgorithm is a base class to implementing different + /// LoadBalancingAlgorithm for multiple . + /// + public abstract class AbstractLoadBalancingAlgorithm : IRequestDistributor + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + protected internal readonly IList> RequestDistributors; + + protected internal AbstractLoadBalancingAlgorithm(IList> requestDistributors) + { + RequestDistributors = requestDistributors; + } + + /// + /// {@inheritDoc} + /// + public virtual bool Operational + { + get { return RequestDistributors.Any(e => e.Operational); } + } + + /// + /// {@inheritDoc} + /// + public virtual TR TrySubmitRequest(IRemoteRequestFactory requestFactory) + where TR : RemoteRequest + where TE : Exception + { + return TrySubmitRequest(InitialConnectionFactoryIndex, requestFactory); + } + + protected internal virtual TR TrySubmitRequest(int initialIndex, + IRemoteRequestFactory requestFactory) + where TR : RemoteRequest + where TE : Exception + { + int index = initialIndex; + int maxIndex = RequestDistributors.Count; + do + { + IRequestDistributor factory = RequestDistributors[index]; + TR result = factory.TrySubmitRequest(requestFactory); + if (null != result) + { + return result; + } + index = (index + 1)%maxIndex; + } while (index != initialIndex); + + /* + * All factories are offline so give up. + */ + return null; + } + + protected internal abstract int InitialConnectionFactoryIndex { get; } + } + + #endregion + + #region FailoverLoadBalancingAlgorithm + + /// + /// A fail-over load balancing algorithm provides fault tolerance across multiple + /// underlying s. + /// + /// This algorithm is typically used for load-balancing between data + /// centers, where there is preference to always forward connection requests to + /// the closest available data center. This algorithm contrasts with the + /// which is used for load-balancing + /// within a data center. + /// + /// + /// This algorithm selects s based on the order in + /// which they were provided during construction. More specifically, an attempt + /// to obtain a will always return the + /// + /// first + /// operational + /// + /// in the list. Applications should, + /// therefore, organize the connection factories such that the preferred + /// (usually the closest) appear before those which + /// are less preferred. + /// + /// + /// If a problem occurs that temporarily prevents connections from being obtained + /// for one of the , then this algorithm automatically + /// "fails over" to the next operational in the list. + /// If none of the are operational then a {@code null} + /// is returned to the client. + /// + /// + /// + /// + /// + public class FailoverLoadBalancingAlgorithm : AbstractLoadBalancingAlgorithm + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + public FailoverLoadBalancingAlgorithm(IList> requestDistributors) + : base(requestDistributors) + { + } + + protected internal override int InitialConnectionFactoryIndex + { + get { return 0; } + } + } + + #endregion + + #region LocalRequest + + /// + /// A LocalRequest represents a remotely requested procedure call locally. + ///

+ /// The and LocalRequest are the representation of the same + /// call on caller and receiver side. + ///

+ public abstract class LocalRequest : IDisposable + where TE : Exception + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + private readonly long _requestId; + + private readonly TP _remoteConnectionContext; + + protected internal LocalRequest(long requestId, TH socket) + { + _requestId = requestId; + _remoteConnectionContext = socket.RemoteConnectionContext; + _remoteConnectionContext.RemoteConnectionGroup.ReceiveRequest>(this); + } + + /// + /// Check if this object was {@ref Inconsistent}-ed and don't dispose. + /// + /// 'true' when object is still active or 'false' when this can be + /// disposed. + public abstract bool Check(); + + /// + /// Signs that the object state is inconsistent. + /// + public abstract void Inconsistent(); + + protected internal abstract bool TryHandleResult(TV result); + + protected internal abstract bool TryHandleError(TE error); + + protected internal abstract bool TryCancel(); + + public long RequestId + { + get { return _requestId; } + } + + public TP RemoteConnectionContext + { + get { return _remoteConnectionContext; } + } + + public void Dispose() + { + _remoteConnectionContext.RemoteConnectionGroup.RemoveRequest(RequestId); + TryCancel(); + } + + public void HandleResult(TV result) + { + _remoteConnectionContext.RemoteConnectionGroup.RemoveRequest(RequestId); + TryHandleResult(result); + } + + public void HandleError(TE error) + { + _remoteConnectionContext.RemoteConnectionGroup.RemoveRequest(RequestId); + TryHandleError(error); + } + + public virtual void HandleIncomingMessage(TH sourceConnection, Object message) + { + throw new NotSupportedException("This request does not supports"); + } + } + + #endregion + + #region MessageListener + + public interface IMessageListener + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + /// + /// + /// Invoked when the opening handshake has been completed for a specific + /// instance. + /// + /// + /// + /// the newly connected + /// + void OnConnect(TH socket); + + /// + /// + /// Invoked when has been called on a + /// particular instance. + /// + /// + /// + /// + /// + /// the being closed. + /// + /// + /// the closing code sent by the remote end-point. + /// + /// + /// the closing reason sent by the remote end-point. + /// + void OnClose(TH socket, int code, string reason); + + /// + /// + /// Invoked when the is open and an error + /// occurs processing the request. + /// + /// + /// + void OnError(Exception t); + + /// + /// + /// Invoked when has been + /// called on a particular instance. + /// + /// + /// + /// the that received a message. + /// + /// + /// the message received. + /// + void OnMessage(TH socket, byte[] data); + + /// + /// + /// Invoked when has been + /// called on a particular instance. + /// + /// + /// + /// the that received a message. + /// + /// + /// the message received. + /// + void OnMessage(TH socket, string data); + + /// + /// + /// Invoked when has been + /// called on a particular instance. + /// + /// + /// + /// the that received the ping. + /// + /// + /// the payload of the ping frame, if any. + /// + void OnPing(TH socket, byte[] bytes); + + /// + /// + /// Invoked when has been + /// called on a particular instance. + /// + /// + /// + /// the that received the pong. + /// + /// + /// the payload of the pong frame, if any. + /// + void OnPong(TH socket, byte[] bytes); + } + + #endregion + + #region RemoteConnectionContext + + /// + /// A RemoteConnectionContext is a custom context to provide application specific + /// information to create the + /// . + ///

+ /// The may depends on + /// which + /// distributes the request. Instance of this class is provided to + /// to produce the + /// before + /// + /// sending the message. + ///

+ public interface IRemoteConnectionContext + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + /// + /// Return the + ///

+ /// Return the + /// in which this instance belongs to. + ///

+ /// + /// the + /// in + /// which this instance belongs to. + /// + TG RemoteConnectionGroup { get; } + } + + #endregion + + #region RemoteConnectionGroup + + public abstract class RemoteConnectionGroup : IRequestDistributor + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + public abstract class AsyncMessageQueueRecord : IDisposable + { +#if DEBUG + private static Int32 _counter; + private Int32 _id; + + public Int32 Id + { + get + { + if (_id == 0) + { + _id = Interlocked.Increment(ref _counter); + } + return _id; + } + } +#endif + private Int32 _busy; + private bool _canFail; + private readonly HashSet _references = new HashSet(); + private readonly TaskCompletionSource _completionHandler = new TaskCompletionSource(); + + protected abstract Task DoSend(TH connection); + + public bool Accept(TH connection) + { + if (!_canFail && !_completionHandler.Task.IsCompleted) + { + return _references.Add(connection); + } + return false; + } + + /// + /// Called when ConnnectionHolder will not try to write this message any more + /// + public void Detach(TH connection) + { + if (_references.Remove(connection) && !_references.Any()) + { + NotifyFailureAndRecycle(new Exception("No Connection is available to send the message")); + } + } + + public async Task SendAsync(TimeSpan timeout) + { +#if DEBUG + Trace.TraceInformation("Sending Async Message {0}", Id); +#endif + _canFail = true; + if (!_completionHandler.Task.IsCompleted) + { + if (_busy == 1 || _references.Any()) + { + await Task.WhenAny(_completionHandler.Task, Task.Run(async () => + { + do + { + await Task.Delay(timeout); + _references.Clear(); + } while (_busy == 1); + NotifyFailureAndRecycle(new TimeoutException("WriteTimeout reached")); + })); + } + else + { + _completionHandler.SetException(new Exception("No Connection was available to send the message")); + } + } + return await _completionHandler.Task; + } + + /// + /// Called when ConnnectionHolder try to gain exclusive access to write this message. + /// + /// + public async Task AcquireAndTryComplete(TH connection) + { + if (null != connection && !_completionHandler.Task.IsCompleted && + Interlocked.Increment(ref _busy) == 1) + { + try + { + if (_references.Remove(connection)) + { +#if DEBUG + Debug.WriteLine("Sending message:{0} over connection:{1}:{2} bussy:{3}", Id, + connection.Id, connection.GetType().FullName, _busy); +#endif + await DoSend(connection); + //Sent successfully + NotifyCompleteAndRecycle(connection); +#if DEBUG + Debug.WriteLine("Sent message:{0} over connection:{1} completed:{2}", Id, + connection.Id, _completionHandler.Task.IsCompleted); +#endif + } + } + catch (Exception e) + { + if (!_references.Any()) + { + NotifyFailureAndRecycle(e); + } + } + finally + { + _busy = 0; + } + return true; + } + return _completionHandler.Task.IsCompleted; + } + + public void Dispose() + { + _completionHandler.SetCanceled(); + _references.Clear(); + Recycle(); + } + + protected virtual void NotifyFailureAndRecycle(Exception e) + { + if (_canFail && !_completionHandler.Task.IsCompleted) + { + _completionHandler.SetException(e); + _references.Clear(); + Recycle(); + } + } + + protected virtual void NotifyCompleteAndRecycle(TH connection) + { + _completionHandler.SetResult(connection); + _references.Clear(); + Recycle(); + } + + protected void Recycle() + { + } + } + + protected readonly ConcurrentDictionary RemoteRequests = + new ConcurrentDictionary(); + + protected readonly ConcurrentDictionary LocalRequests = + new ConcurrentDictionary(); + + protected readonly ConcurrentDictionary WebSockets = new ConcurrentDictionary(); + + private Int64 _messageId; + + protected RemoteConnectionGroup(string remoteSessionId) + { + RemoteSessionId = remoteSessionId; + } + + public abstract bool Operational { get; } + + public string RemoteSessionId { get; internal set; } + + protected internal abstract TP RemoteConnectionContext { get; } + + protected internal virtual Int64 NextRequestId + { + get + { + Int64 next = Interlocked.Increment(ref _messageId); + while (next == 0) + { + next = Interlocked.Increment(ref _messageId); + } + return next; + } + } + + protected internal virtual TR AllocateRequest( + IRemoteRequestFactory requestFactory) + where TR : RemoteRequest + where TE : Exception + { + Int64 newMessageId; + TR request; + Action> completionCallback = x => + { + dynamic ignored; + RemoteRequests.TryRemove(x.RequestId, out ignored); + }; + + do + { + newMessageId = NextRequestId; + request = requestFactory.CreateRemoteRequest(RemoteConnectionContext, newMessageId, completionCallback); + if (null == request) + { + break; + } + } while (!RemoteRequests.TryAdd(newMessageId, request)); + return request; + } + + public TV TrySendMessage(Func>, TV> function) + { + return function(messsage => + { + foreach (var connection in WebSockets.Keys) + { + connection.Enqueue(messsage); + } + return messsage.SendAsync(TimeSpan.FromMinutes(2)); + }); + } + + // -- Pair of methods to Submit and Receive the new Request Start -- + public virtual TR TrySubmitRequest(IRemoteRequestFactory requestFactory) + where TR : RemoteRequest + where TE : Exception + { + TR remoteRequest = AllocateRequest(requestFactory); + if (null != remoteRequest) + { + Task result = TrySendMessage(remoteRequest.SendFunction); + if (null == result) + { + dynamic ignored; + RemoteRequests.TryRemove(remoteRequest.RequestId, out ignored); + remoteRequest = null; + } + else if (remoteRequest.Promise == null) + { + Trace.TraceError("FATAL: Concurrency Problem! Prmoise can not be null if result is not null."); + } + } + return remoteRequest; + } + + public virtual TR ReceiveRequest(TR localRequest) + where TE : Exception + where TR : LocalRequest + { + TR tmp = LocalRequests.GetOrAdd(localRequest.RequestId, localRequest); + if (null != tmp && !tmp.Equals(localRequest)) + { + throw new InvalidOperationException("Request has been registered with id: " + localRequest.RequestId); + } + return localRequest; + } + + // -- Pair of methods to Submit and Receive the new Request End -- + // -- Pair of methods to Cancel pending Request Start -- + + public dynamic /*RemoteRequest*/ SubmitRequestCancel(Int64 messageId) + { + dynamic tmp; + RemoteRequests.TryRemove(messageId, out tmp); + if (null != tmp) + { + tmp.Cancel(); + } + return tmp; + } + + public dynamic /*LocalRequest*/ ReceiveRequestCancel(Int64 messageId) + { + dynamic tmp; + LocalRequests.TryRemove(messageId, out tmp); + if (null != tmp) + { + tmp.Cancel(); + } + return tmp; + } + + // -- Pair of methods to Cancel pending Request End -- + // -- Pair of methods to Communicate pending Request Start -- + public dynamic /*RemoteRequest*/ ReceiveRequestResponse(TH sourceConnection, + Int64 messageId, Object message) + { + dynamic tmp; + RemoteRequests.TryGetValue(messageId, out tmp); + if (null != tmp) + { + tmp.HandleIncomingMessage(sourceConnection, message); + } + return tmp; + } + + public dynamic /*LocalRequest*/ ReceiveRequestUpdate(TH sourceConnection, + Int64 messageId, Object message) + { + dynamic tmp; + LocalRequests.TryGetValue(messageId, out tmp); + if (null != tmp) + { + tmp.HandleIncomingMessage(sourceConnection, message); + } + return tmp; + } + + // -- Pair of methods to Communicate pending Request End -- + public dynamic /*LocalRequest*/ RemoveRequest(Int64 messageId) + { + dynamic value; + LocalRequests.TryRemove(messageId, out value); + return value; + } + } + + #endregion + + #region RemoteConnectionHolder + + /// + /// A RemoteConnectionHolder is a wrapper class for the underlying communication + /// chanel. + ///

+ /// The API is detached from the real underlying protocol and abstracted through + /// this interface. The message transmitted via this implementation should + /// trigger the appropriate method on + /// . + ///

+ public interface IRemoteConnectionHolder : IDisposable + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { +#if DEBUG + Int32 Id { get; } +#endif + + TP RemoteConnectionContext { get; } + + void Enqueue(RemoteConnectionGroup.AsyncMessageQueueRecord record); + + + /// + /// Initiates the asynchronous transmission of a binary message. This method + /// returns before the message is transmitted. Developers may use the + /// returned Future object to track progress of the transmission. + /// + /// + /// The data to be sent over the connection. + /// + /// The token that propagates the notification that operations should be canceled. + /// the Future object representing the send operation. + Task SendBytesAsync(byte[] data, CancellationToken cancellationToken); + + /// + /// Initiates the asynchronous transmission of a string message. This method + /// returns before the message is transmitted. Developers may use the + /// returned Future object to track progress of the transmission. + /// + /// + /// the data being sent + /// + /// The token that propagates the notification that operations should be canceled. + /// the Future object representing the send operation. + Task SendStringAsync(string data, CancellationToken cancellationToken); + + /// + /// Send a Ping message containing the given application data to the remote + /// endpoint. The corresponding Pong message may be picked up using the + /// MessageHandler.Pong handler. + /// + /// + /// the data to be carried in the ping request + /// + void SendPing(byte[] applicationData); + + /// + /// Allows the developer to send an unsolicited Pong message containing the + /// given application data in order to serve as a unidirectional heartbeat + /// for the session. + /// + /// + /// the application data to be carried in the pong response. + /// + void SendPong(byte[] applicationData); + } + + #endregion + + #region RemoteRequest + + /// + /// A RemoteRequest represents a locally requested procedure call executed + /// remotely. + ///

+ /// The RemoteRequest and are the representation of the same + /// call on caller and receiver side. + ///

+ public abstract class RemoteRequest : IDisposable + where TE : Exception + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + private readonly TP _context; + private readonly Int64 _requestId; + private readonly Action> _completionCallback; + + private TaskCompletionSource _promise; + private readonly CancellationToken _cancellationToken; + private readonly ReaderWriterLock _lock = new ReaderWriterLock(); + + protected RemoteRequest(TP context, Int64 requestId, + Action> completionCallback, CancellationToken cancellationToken) + { + _context = context; + _requestId = requestId; + _completionCallback = completionCallback; + _cancellationToken = cancellationToken; + } + + /// + /// Check if this object was {@ref Inconsistent}-ed and don't dispose. + /// + /// 'true' when object is still active or 'false' when this can be + /// disposed. + public abstract bool Check(); + + /// + /// Signs that the object state is inconsistent. + /// + public abstract void Inconsistent(); + + public abstract void HandleIncomingMessage(TH sourceConnection, Object message); + + protected internal abstract RemoteConnectionGroup.AsyncMessageQueueRecord CreateMessageElement( + TP remoteContext, long requestId); + + protected internal abstract void TryCancelRemote(TP remoteContext, Int64 requestId); + protected internal abstract TE CreateCancellationException(Exception cancellationException); + + public virtual Int64 RequestId + { + get { return _requestId; } + } + + public virtual Int64 RequestTime { get; internal set; } + + public virtual Task Promise + { + get { return _promise.Task; } + } + + /// + /// Invoked when the asynchronous task has completed successfully. + /// + /// + /// The result of the asynchronous task. + /// + protected internal virtual void HandleResult(TV result) + { + if (!_promise.Task.IsCompleted) + { + try + { + _promise.SetResult(result); + } + catch (InvalidOperationException) + { + //An attempt was made to transition a task to a final state when it had already completed. + } + } + } + + /// + /// Invoked when the asynchronous task has failed. + /// + /// + /// The error indicating why the asynchronous task has failed. + /// + protected internal virtual void HandleError(Exception error) + { + if (!_promise.Task.IsCompleted) + { + try + { + _promise.SetException(error); + } + catch (InvalidOperationException) + { + //An attempt was made to transition a task to a final state when it had already completed. + } + } + } + + protected internal virtual TP ConnectionContext + { + get { return _context; } + } + + + public CancellationToken CancellationToken + { + get { return _cancellationToken; } + } + + public void Dispose() + { + try + { + TryCancelRemote(_context, _requestId); + } + catch (Exception) + { + //Ignore + } + } + + public virtual Func.AsyncMessageQueueRecord, Task>, Task> + SendFunction + { + get + { + return async send => + { + TaskCompletionSource resultPromise = _promise; + if (null == resultPromise) + { + RemoteConnectionGroup.AsyncMessageQueueRecord message = + CreateMessageElement(_context, _requestId); + if (message == null) + { + throw new InvalidOperationException("RemoteRequest has empty message"); + } + + try + { + // Single thread should process it so it should not fail + _lock.AcquireWriterLock(new TimeSpan(0, 1, 0)); + IDisposable cancellationTokenRegistration = null; + try + { + if (null == _promise) + { + _promise = new TaskCompletionSource(); + // ReSharper disable once ImpureMethodCallOnReadonlyValueField + cancellationTokenRegistration = _cancellationToken.Register(promise => + { + var p = promise as TaskCompletionSource; + if (null != p) + { + p.TrySetCanceled(); + } + Dispose(); + }, _promise); + _promise.Task.ContinueWith(x => { _completionCallback(this); }) + .ConfigureAwait(false); + if (_cancellationToken.IsCancellationRequested) + { + _promise.SetCanceled(); + } + else + { + await send(message); + } + RequestTime = DateTime.Now.Ticks; + } + } + catch (Exception) + { + _promise = null; + if (null != cancellationTokenRegistration) + { + cancellationTokenRegistration.Dispose(); + } + throw; + } + finally + { + _lock.ReleaseWriterLock(); + } + } + catch (ApplicationException) + { + // The writer lock request timed out. + } + + return null != _promise ? await _promise.Task : default(TV); + } + return await resultPromise.Task; + }; + } + } + } + + #endregion + + #region RemoteRequestFactory + + /// + /// A RemoteRequestFactory creates a new + /// aware + /// before sending in + /// . + /// + public interface IRemoteRequestFactory + where TR : RemoteRequest + where TE : Exception + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + TR CreateRemoteRequest(TP context, long requestId, Action> completionCallback); + } + + #endregion + + #region RequestDistributor + + /// + /// A RequestDistributor delivers the + /// to the connected + /// endpoint. + ///

+ /// The is used to + /// create a + /// aware which will be + /// delivered. + ///

+ /// The implementation may hold multiple transmission channels and try all to + /// deliver the message before if fails. + ///

+ /// The failed delivery signaled with null empty to avoid the expensive Throw and + /// Catch especially when many implementation are chained together. + ///

+ /// + /// + /// + public interface IRequestDistributor + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + /// + /// the factory to create the + /// + /// + /// + /// type of + /// + /// + /// The type of the task's result, or if the task + /// does not return anything (i.e. it only has side-effects). + /// + /// + /// The type of the exception thrown by the task if it fails + /// + /// new promise if succeeded otherwise {@code null}. + TR TrySubmitRequest(IRemoteRequestFactory requestFactory) + where TR : RemoteRequest + where TE : Exception; + + /// + /// Check if this implementation is operational. + /// + /// + /// {@code true} is operational and ready the submit + /// + /// otherwise {@code false}. + /// + bool Operational { get; } + } + + #endregion + + #region RoundRobinLoadBalancingAlgorithm + + /// + /// A round robin load balancing algorithm distributes + /// s across a list of + /// s one at a time. When the end of the list is + /// reached, the algorithm starts again from the beginning. + /// + /// This algorithm is typically used for load-balancing within data + /// centers, where load must be distributed equally across multiple servers. This + /// algorithm contrasts with the which is + /// used for load-balancing between data centers. + /// + /// + /// If a problem occurs that temporarily prevents connections from being obtained + /// for one of the s, then this algorithm automatically + /// "fails over" to the next operational in the list. + /// If none of the are operational then a {@code null} + /// is returned to the client. + /// + /// + /// + /// + /// + public class RoundRobinLoadBalancingAlgorithm : AbstractLoadBalancingAlgorithm + where TG : RemoteConnectionGroup + where TH : IRemoteConnectionHolder + where TP : IRemoteConnectionContext + { + private Int32 _counter; + + public RoundRobinLoadBalancingAlgorithm(IList> requestDistributors) + : base(requestDistributors) + { + } + + protected internal override int InitialConnectionFactoryIndex + { + get + { + // A round robin pool of one connection factories is unlikely in + // practice and requires special treatment. + int maxSize = RequestDistributors.Count(); + if (maxSize == 1) + { + return 0; + } + return (Interlocked.Increment(ref _counter) & 0x7fffffff)%maxSize; + } + } + } + + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkRPC/version.template b/dotnet/framework/FrameworkRPC/version.template new file mode 100755 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/FrameworkRPC/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/Async.cs b/dotnet/framework/FrameworkServer/Async.cs new file mode 100755 index 00000000..f47a35c9 --- /dev/null +++ b/dotnet/framework/FrameworkServer/Async.cs @@ -0,0 +1,663 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Spi; + +namespace Org.ForgeRock.OpenICF.Framework.Remote +{ + + #region IAsyncConnectorFacade + + /// + /// A AsyncConnectorFacade. + /// + /// since 1.5 + public interface IAsyncConnectorFacade : ConnectorFacade, IAuthenticationAsyncApiOp, ICreateAsyncApiOp, + IDeleteAsyncApiOp, IGetAsyncApiOp, IResolveUsernameAsyncApiOp, ISchemaAsyncApiOp, IScriptOnConnectorAsyncApiOp, + IScriptOnResourceAsyncApiOp, ITestAsyncApiOp, IUpdateAsyncApiOp, + IValidateAsyncApiOp + { + } + + #endregion + + #region IAsyncConnectorInfoManager + + /// + /// An IAsyncConnectorInfoManager maintains a list of ConnectorInfo + /// instances, each of which describes a connector that is available. + /// + /// since 1.5 + public interface IAsyncConnectorInfoManager : ConnectorInfoManager + { + /// + /// Add a promise which will be fulfilled with the + /// for the given + /// {@ConnectorKey}. + /// Add a Promise which will be fulfilled immediately if the + /// is maintained + /// currently by this instance or later when it became available. + /// + /// + /// new promise + Task FindConnectorInfoAsync(ConnectorKey key); + + /// + /// Add a promise which will be fulfilled with the + /// for the given + /// {@ConnectorKeyRange}. + /// Add a Promise which will be fulfilled immediately if the + /// is maintained + /// currently by this instance or later when it became available. + /// There may be multiple ConnectorInfo matching the range. The + /// implementation can only randomly fulfill the promise. It can not grantee + /// the highest version to return because it may became available after the + /// promised added and after a lower version of ConnectorInfo became + /// available in this manager. + /// + /// + /// new promise + Task FindConnectorInfoAsync(ConnectorKeyRange keyRange); + } + + #endregion + + #region IAuthenticationAsyncApiOp + + public interface IAuthenticationAsyncApiOp : AuthenticationApiOp + { + /// + /// Most basic authentication available. + /// + /// + /// The object class to use for authenticate. Will typically be an + /// account. Must not be null. + /// + /// + /// string that represents the account or user id. + /// + /// + /// string that represents the password for the account or user. + /// + /// + /// additional options that impact the way this operation is run. + /// May be null. + /// + /// + /// Uid The uid of the account that was used to authenticate + /// + /// if the credentials do not pass authentication otherwise + /// nothing. + /// + Task AuthenticateAsync(ObjectClass objectClass, String username, GuardedString password, + OperationOptions options, CancellationToken cancellationToken); + } + + #endregion + + #region DisposableAsyncConnectorInfoManager + + public abstract class DisposableAsyncConnectorInfoManager : IAsyncConnectorInfoManager, IDisposable + where T : DisposableAsyncConnectorInfoManager + { + internal Int32 IsRunning = 1; + + protected abstract void DoClose(); + + public virtual bool Running + { + get { return IsRunning != 0; } + } + + public void Dispose() + { + if (CanCloseNow()) + { + try + { + DoClose(); + } + catch (Exception) + { + //logger.ok(t, "Failed to close {0}", this); + } + // Notify CloseListeners + OnDisposed(); + } + } + + protected internal virtual bool CanCloseNow() + { + return (Interlocked.CompareExchange(ref IsRunning, 0, 1) == 1); + } + + /// + /// Adds a event handler to listen to the Disposed event on the DisposableAsyncConnectorInfoManager. + /// + private event EventHandler DisposedEvent; + + public event EventHandler Disposed + { + add + { + // check if this is still running + if (IsRunning == 1) + { + // add close listener + DisposedEvent += value; + // check the its state again + if (DisposedEvent != null && (IsRunning != 1 && DisposedEvent.GetInvocationList().Contains(value))) + { + // if this was closed during the method call - notify the + // listener + try + { + value(this, EventArgs.Empty); + } + catch (Exception) + { + // ignored + } + } + } // if this is closed - notify the listener + else + { + try + { + value(this, EventArgs.Empty); + } + catch (Exception) + { + // ignored + } + } + } + remove { DisposedEvent -= value; } + } + + protected virtual void OnDisposed() + { + try + { + var handler = DisposedEvent; + if (handler != null) handler(this, EventArgs.Empty); + } + catch (Exception) + { + //logger.ok(ignored, "CloseListener failed"); + } + } + + + public abstract IList ConnectorInfos { get; } + public abstract ConnectorInfo FindConnectorInfo(ConnectorKey key); + public abstract Task FindConnectorInfoAsync(ConnectorKey key); + public abstract Task FindConnectorInfoAsync(ConnectorKeyRange keyRange); + } + + #endregion + + #region ICreateAsyncApiOp + + public interface ICreateAsyncApiOp : CreateApiOp + { + /// + /// Create a target object based on the specified attributes. + /// The Connector framework always requires attribute + /// ObjectClass. The Connector itself may require + /// additional attributes. The API will confirm that the set contains the + /// ObjectClass attribute and that no two attributes in the set + /// have the same . + /// + /// + /// the type of object to create. Must not be null. + /// + /// + /// includes all the attributes necessary to create the target + /// object (including the ObjectClass attribute). + /// + /// + /// additional options that impact the way this operation is run. + /// May be null. + /// + /// + /// + /// the unique id for the object that is created. For instance in + /// LDAP this would be the 'dn', for a database this would be the + /// primary key, and for 'ActiveDirectory' this would be the GUID. + /// + /// + /// if ObjectClass is missing or elements of the set + /// produce duplicate values of . + /// + /// + /// if the parameter createAttributes is + /// null. + /// + /// + /// if the + /// SPI throws a native . + /// + Task CreateAsync(ObjectClass objectClass, ICollection createAttributes, + OperationOptions options, CancellationToken cancellationToken); + } + + #endregion + + #region IDeleteAsyncApiOp + + public interface IDeleteAsyncApiOp : DeleteApiOp + { + /// + /// Delete the object that the specified Uid identifies (if any). + /// + /// + /// type of object to delete. + /// + /// + /// The unique id that specifies the object to delete. + /// + /// + /// additional options that impact the way this operation is run. + /// May be null. + /// + /// + /// + /// if the + /// + /// does not exist on the resource. + /// + /// + /// if a problem occurs during the operation (for instance, an + /// operational timeout). + /// + Task DeleteAsync(ObjectClass objectClass, Uid uid, OperationOptions options, CancellationToken cancellationToken); + } + + #endregion + + #region IGetAsyncApiOp + + public interface IGetAsyncApiOp : GetApiOp + { + /// + /// Get a particular + /// + /// based on the . + /// + /// + /// type of object to get. + /// + /// + /// the unique id of the object that to get. + /// + /// + /// additional options that impact the way this operation is run. + /// May be null. + /// + /// + /// + /// + /// based on the + /// + /// provided or null if no such object could be found. + /// + Task GetObjectAsync(ObjectClass objectClass, Uid uid, OperationOptions options, + CancellationToken cancellationToken); + } + + #endregion + + #region IResolveUsernameAsyncApiOp + + public interface IResolveUsernameAsyncApiOp : ResolveUsernameApiOp + { + /// + /// Resolve the given + /// {@link org.identityconnectors.framework.api.operations.AuthenticationApiOp + /// authentication} username to the corresponding + /// . + /// The Uid is the one that + /// + /// would return in case of a successful authentication. + /// + /// + /// The object class to use for authenticate. Will typically be an + /// account. Must not be null. + /// + /// + /// string that represents the account or user id. + /// + /// + /// additional options that impact the way this operation is run. + /// May be null. + /// + /// + /// Uid The uid of the account that would be used to authenticate. + /// + /// if the username could not be resolved. + /// @since 1.5 + /// + Task ResolveUsernameAsync(ObjectClass objectClass, String username, OperationOptions options, + CancellationToken cancellationToken); + } + + #endregion + + #region ISchemaAsyncApiOp + + public interface ISchemaAsyncApiOp : SchemaApiOp + { + /// + /// Retrieve the basic schema of this + /// . + /// + /// + Task SchemaAsync(CancellationToken cancellationToken); + } + + #endregion + + #region IScriptOnConnectorAsyncApiOp + + public interface IScriptOnConnectorAsyncApiOp : ScriptOnConnectorApiOp + { + /// + /// Runs the script. + /// + /// + /// The script and arguments to run. + /// + /// + /// Additional options that control how the script is run. The + /// framework does not currently recognize any options but + /// specific connectors might. Consult the documentation for each + /// connector to identify supported options. + /// + /// + /// + /// The result of the script. The return type must be a type that the + /// framework supports for serialization. + /// + Task RunScriptOnConnectorAsync(ScriptContext request, OperationOptions options, + CancellationToken cancellationToken); + } + + #endregion + + #region IScriptOnResourceAsyncApiOp + + public interface IScriptOnResourceAsyncApiOp : ScriptOnResourceApiOp + { + /// + /// Runs a script on a specific target resource. + /// + /// + /// The script and arguments to run. + /// + /// + /// Additional options which control how the script is run. Please + /// refer to the connector documentation for supported options. + /// + /// + /// + /// The result of the script. The return type must be a type that the + /// connector framework supports for serialization. See + /// + /// for a list of supported return types. + /// + Task RunScriptOnResourceAsync(ScriptContext request, OperationOptions options, + CancellationToken cancellationToken); + } + + #endregion + + #region ITestAsyncApiOp + + public interface ITestAsyncApiOp : TestApiOp + { + /// + /// Tests the {@link org.identityconnectors.framework.api.APIConfiguration + /// Configuration} with the connector. + /// + /// + /// + /// if the configuration is not valid or the test failed. + /// + Task TestAsync(CancellationToken cancellationToken); + } + + #endregion + + #region IUpdateAsyncApiOp + + public interface IUpdateAsyncApiOp : UpdateApiOp + { + /// + /// Update the object specified by the + /// and + /// , replacing + /// the current values of each attribute with the values provided. + /// + /// For each input attribute, replace all of the current values of that + /// attribute in the target object with the values of that attribute. + /// + /// + /// If the target object does not currently contain an attribute that the + /// input set contains, then add this attribute (along with the provided + /// values) to the target object. + /// + /// + /// If the value of an attribute in the input set is {@code null}, then do + /// one of the following, depending on which is most appropriate for the + /// target: + ///
    + ///
  • + /// If possible, remove that attribute from the target object + /// entirely. + ///
  • + ///
  • + /// Otherwise, replace all of the current values of that + /// attribute in the target object with a single value of {@code null}. + ///
  • + ///
+ ///
+ ///
+ /// + /// the type of object to modify. Must not be null. + /// + /// + /// the uid of the object to modify. Must not be null. + /// + /// + /// set of new + /// + /// . the values in this set represent the new, merged values to + /// be applied to the object. This set may also include + /// {@link Org.IdentityConnectors.Framework.Common.Objects.OperationalAttributes + /// operational attributes}. Must not be null. + /// + /// + /// additional options that impact the way this operation is run. + /// May be null. + /// + /// + /// + /// the + /// of the updated object in case the update changes the formation of + /// the unique identifier. + /// + /// + /// if the + /// + /// does not exist on the resource. + /// + Task UpdateAsync(ObjectClass objectClass, Uid uid, ICollection replaceAttributes, + OperationOptions options, CancellationToken cancellationToken); + + /// + /// Update the object specified by the and , + /// adding to the current values of each attribute the values provided. + /// + /// For each attribute that the input set contains, add to the current values + /// of that attribute in the target object all of the values of that + /// attribute in the input set. + /// + /// + /// NOTE that this does not specify how to handle duplicate values. The + /// general assumption for an attribute of a {@code ConnectorObject} is that + /// the values for an attribute may contain duplicates. Therefore, in general + /// simply append the provided values to the current value for each + /// attribute. + /// + /// + /// IMPLEMENTATION NOTE: for connectors that merely implement + /// and not + /// + /// this method will be simulated by fetching, merging, and calling + /// + /// . Therefore, connector implementations are encourage to implement + /// + /// from a performance and atomicity standpoint. + /// + /// + /// + /// the type of object to modify. Must not be null. + /// + /// + /// the uid of the object to modify. Must not be null. + /// + /// + /// set of deltas. The values for the attributes + /// in this set represent the values to add to attributes in the + /// object. merged. This set must not include + /// {@link Org.IdentityConnectors.Framework.Common.Objects.OperationalAttributes + /// operational attributes}. Must not be null. + /// + /// + /// additional options that impact the way this operation is run. + /// May be null. + /// + /// + /// + /// the of the updated object in case the update changes + /// the formation of the unique identifier. + /// + /// + /// if the does not exist on the resource. + /// + Task AddAttributeValuesAsync(ObjectClass objclass, Uid uid, ICollection valuesToAdd, + OperationOptions options, CancellationToken cancellationToken); + + /// + /// Update the object specified by the and , + /// removing from the current values of each attribute the values provided. + /// + /// For each attribute that the input set contains, remove from the current + /// values of that attribute in the target object any value that matches one + /// of the values of the attribute from the input set. + /// + /// + /// NOTE that this does not specify how to handle unmatched values. The + /// general assumption for an attribute of a {@code ConnectorObject} is that + /// the values for an attribute are merely representational state. + /// Therefore, the implementer should simply ignore any provided value that + /// does not match a current value of that attribute in the target object. + /// Deleting an unmatched value should always succeed. + /// + /// + /// IMPLEMENTATION NOTE: for connectors that merely implement + /// and not + /// + /// this method will be simulated by fetching, merging, and calling + /// + /// . Therefore, connector implementations are encourage to implement + /// + /// from a performance and atomicity standpoint. + /// + /// + /// + /// the type of object to modify. Must not be null. + /// + /// + /// the uid of the object to modify. Must not be null. + /// + /// + /// set of deltas. The values for the attributes + /// in this set represent the values to remove from attributes in + /// the object. merged. This set must not include + /// {@link Org.IdentityConnectors.Framework.Common.Objects.OperationalAttributes + /// operational attributes}. Must not be null. + /// + /// + /// additional options that impact the way this operation is run. + /// May be null. + /// + /// + /// + /// the of the updated object in case the update changes + /// the formation of the unique identifier. + /// + /// + /// if the does not exist on the resource. + /// + Task RemoveAttributeValuesAsync(ObjectClass objclass, Uid uid, + ICollection valuesToRemove, OperationOptions options, + CancellationToken cancellationToken); + } + + #endregion + + #region IValidateAsyncApiOp + + public interface IValidateAsyncApiOp : ValidateApiOp + { + /// + /// Validates the + /// {@link org.identityconnectors.framework.api.APIConfiguration + /// configuration}. + /// + /// + /// + /// if the configuration is not valid. + /// + Task ValidateAsync(CancellationToken cancellationToken); + } + + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/AsyncImpl.cs b/dotnet/framework/FrameworkServer/AsyncImpl.cs new file mode 100755 index 00000000..fede411f --- /dev/null +++ b/dotnet/framework/FrameworkServer/AsyncImpl.cs @@ -0,0 +1,2989 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Google.Protobuf; +using Org.ForgeRock.OpenICF.Common.RPC; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Impl.Api.Local.Operations; +using API = Org.IdentityConnectors.Framework.Api; +using PRB = Org.ForgeRock.OpenICF.Common.ProtoBuf; + +namespace Org.ForgeRock.OpenICF.Framework.Remote +{ + + #region AbstractAPIOperation + + public abstract class AbstractAPIOperation + { + public static readonly ConnectionFailedException FailedExceptionMessage = + new ConnectionFailedException("No remote Connector Server is available at this moment"); + + protected AbstractAPIOperation( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + { + RemoteConnection = Assertions.NullChecked(remoteConnection, "remoteConnection"); + ConnectorKey = Assertions.NullChecked(connectorKey, "connectorKey"); + FacadeKeyFunction = Assertions.NullChecked(facadeKeyFunction, "facadeKeyFunction"); + Timeout = Math.Max(timeout, API.APIConstants.NO_TIMEOUT); + } + + public API.ConnectorKey ConnectorKey { get; internal set; } + + public long Timeout { get; internal set; } + + protected internal + IRequestDistributor + RemoteConnection { get; internal set; } + + protected internal Func FacadeKeyFunction { get; internal set; } + + + protected internal virtual Task SubmitRequest( + AbstractRemoteOperationRequestFactory requestFactory) + where TR : AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + { + TR request = RemoteConnection.TrySubmitRequest(requestFactory); + if (null != request) + { + return request.Promise; + } + TaskCompletionSource result = new TaskCompletionSource(); + result.SetException(FailedExceptionMessage); + return result.Task; + } + } + + internal abstract class ResultBuffer + { + internal class Pair + { + public T Item { get; set; } + public TR Result { get; set; } + } + + private readonly int _timeoutMillis; + private volatile Boolean _stopped = false; + private readonly BlockingCollection _queue = new BlockingCollection(new ConcurrentQueue()); + + private readonly ConcurrentDictionary _buffer = new ConcurrentDictionary(); + + private Int64 _nextPermit = 1; + + private long _lastSequenceNumber = -1; + + protected ResultBuffer(long timeoutMillis) + { + + if (timeoutMillis == API.APIConstants.NO_TIMEOUT) + { + _timeoutMillis = int.MaxValue; + } + else if (timeoutMillis == 0) + { + _timeoutMillis = 60 * 1000; + } + else + { + _timeoutMillis = unchecked((int)timeoutMillis); + } + } + + public virtual bool Stopped + { + get + { + return _stopped; + } + } + + public virtual bool HasLast() + { + return _lastSequenceNumber > 0; + } + + public virtual bool HasAll() + { + return HasLast() && _nextPermit > _lastSequenceNumber; + } + + public virtual int Remaining + { + get + { + return _queue.Count; + } + } + + public virtual void Clear() + { + _stopped = true; + _buffer.Clear(); + Pair item; + while (_queue.TryTake(out item)) + { + // do nothing + } + } + + public virtual void ReceiveNext(long sequence, T result) + { + if (null != result) + { + if (_nextPermit == sequence) + { + Enqueue(new Pair { Item = result }); + } + else + { + _buffer[sequence] = new Pair { Item = result }; + } + } + // Feed the queue + Pair o = null; + while ((_buffer.TryRemove(_nextPermit, out o))) + { + Enqueue(o); + } + } + + public virtual void ReceiveLast(long resultCount, TR result) + { + if (0 == resultCount && null != result) + { + // Empty result set + Enqueue(new Pair { Result = result }); + _lastSequenceNumber = 0; + } + else + { + long idx = resultCount + 1; + _buffer[idx] = new Pair { Result = result }; + _lastSequenceNumber = idx; + } + + if (_lastSequenceNumber == _nextPermit) + { + // Operation finished + Enqueue(new Pair { Result = result }); + } + else + { + ReceiveNext(_nextPermit, default(T)); + } + } + + protected internal virtual void Enqueue(Pair result) + { + // Block if queue is full + if (_queue.TryAdd(result)) + { + // Let the next go through + _nextPermit++; + } + else + { + // What to do? + Trace.TraceInformation("Failed to Enqueue: "); + } + } + + protected internal abstract bool Handle(object result); + + + public void Process() + { + while (!_stopped) + { + + Pair obj; + + if (!_queue.TryTake(out obj, _timeoutMillis)) + { + // we timed out + ReceiveNext(-1L, default(T)); + if (_queue.Any()) + { + Clear(); + throw new OperationTimeoutException(); + } + } + else + { + try + { + Boolean keepGoing = Handle(obj); + if (!keepGoing) + { + // stop and wait + Clear(); + } + } + catch (Exception t) + { + Clear(); + throw new ConnectorException(t.Message, t); + } + } + } + } + } + + #endregion + + #region AbstractLocalOperationProcessor + + public abstract class AbstractLocalOperationProcessor : LocalOperationProcessor + { + protected readonly TM _requestMessage; + + protected internal AbstractLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, TM message) + : base(requestId, socket) + { + _requestMessage = message; + } + + protected internal abstract TV ExecuteOperation(API.ConnectorFacade connectorFacade, TM requestMessage); + + public virtual void Execute(API.ConnectorFacade connectorFacade) + { + try + { + HandleResult(ExecuteOperation(connectorFacade, _requestMessage)); + } + catch (Exception error) + { + HandleError(error); + } + } + } + + #endregion + + #region AbstractRemoteOperationRequestFactory + + public abstract class AbstractRemoteOperationRequestFactory : + IRemoteRequestFactory + + where TR : RemoteOperationRequest + { + private readonly IdentityConnectors.Framework.Api.ConnectorKey _connectorKey; + + private readonly Func _facadeKeyFunction; + + protected AbstractRemoteOperationRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, CancellationToken cancellationToken) + { + _connectorKey = connectorKey; + _facadeKeyFunction = facadeKeyFunction; + CancellationToken = cancellationToken; + } + + protected CancellationToken CancellationToken { get; private set; } + + protected Common.ProtoBuf.ConnectorKey CreateConnectorKey() + { + return new + Common.ProtoBuf.ConnectorKey + { + BundleName = _connectorKey.BundleName, + BundleVersion = _connectorKey.BundleVersion, + ConnectorName = _connectorKey.ConnectorName + }; + } + + protected internal ByteString CreateConnectorFacadeKey(RemoteOperationContext context) + { + return _facadeKeyFunction(context); + } + + protected internal abstract PRB.OperationRequest CreateOperationRequest(RemoteOperationContext remoteContext); + + protected internal virtual PRB.RPCRequest CreateRpcRequest(RemoteOperationContext context) + { + ByteString facadeKey = CreateConnectorFacadeKey(context); + if (null != facadeKey) + { + PRB.OperationRequest operationBuilder = CreateOperationRequest(context); + operationBuilder.ConnectorKey = CreateConnectorKey(); + operationBuilder.ConnectorFacadeKey = facadeKey; + operationBuilder.Locale = + MessagesUtil.SerializeMessage(Thread.CurrentThread.CurrentUICulture); + return new PRB.RPCRequest { OperationRequest = operationBuilder }; + } + return null; + } + + public abstract class AbstractRemoteOperationRequest : RemoteOperationRequest + { + public const string OperationExpectsMessage = "RemoteOperation[{0}] expects {1}"; + + internal readonly PRB.RPCRequest Request; + + protected AbstractRemoteOperationRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + + > completionCallback, PRB.RPCRequest requestBuilder, CancellationToken cancellationToken) + : base(context, requestId, completionCallback, cancellationToken) + { + Request = requestBuilder; + } + + protected internal abstract TM GetOperationResponseMessages(PRB.OperationResponse message); + + protected internal abstract void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + TM message); + + protected internal override PRB.RPCRequest CreateOperationRequest(RemoteOperationContext remoteContext) + { + return Request; + } + + protected internal override bool HandleResponseMessage(WebSocketConnectionHolder sourceConnection, + Object message) + { + var response = message as PRB.OperationResponse; + if (response != null) + { + TM responseMessage = GetOperationResponseMessages(response); + if (null != responseMessage) + { + try + { + HandleOperationResponseMessages(sourceConnection, responseMessage); + } + catch (Exception e) + { + Debug.WriteLine("Failed to handle the result of operation {0}", e); + HandleError(e); + } + return true; + } + } + return false; + } + } + + public abstract TR CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > + completionCallback); + } + + #endregion + + #region AuthenticationAsyncApiOpImpl + + public class AuthenticationAsyncApiOpImpl : AbstractAPIOperation, IAuthenticationAsyncApiOp + { + public AuthenticationAsyncApiOpImpl( + IRequestDistributor + remoteConnection, IdentityConnectors.Framework.Api.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual Uid Authenticate(ObjectClass objectClass, string username, GuardedString password, + OperationOptions options) + { + try + { + return AuthenticateAsync(objectClass, username, password, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public async Task AuthenticateAsync(ObjectClass objectClass, string username, GuardedString password, + OperationOptions options, CancellationToken cancellationToken) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(username, "username"); + Assertions.NullCheck(password, "password"); + + PRB.AuthenticateOpRequest requestBuilder = + new PRB.AuthenticateOpRequest + { + ObjectClass = objectClass.GetObjectClassValue(), + Username = username, + Password = MessagesUtil.SerializeLegacy(password) + }; + + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + return + await + SubmitRequest( + new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, new PRB.OperationRequest { AuthenticateOpRequest = requestBuilder }, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(Org.IdentityConnectors.Framework.Api.ConnectorKey connectorKey, + Func facadeKeyFunction, + PRB.OperationRequest operationRequest, CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, CancellationToken); + } + return null; + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.AuthenticateOpResponse GetOperationResponseMessages( + PRB.OperationResponse message) + { + if (null != message.AuthenticateOpResponse) + { + return message.AuthenticateOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "AuthenticateOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.AuthenticateOpResponse message) + { + HandleResult(null != message.Uid + ? MessagesUtil.DeserializeMessage(message.Uid) + : null); + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor(long requestId, + WebSocketConnectionHolder socket, PRB.AuthenticateOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : AbstractLocalOperationProcessor + { + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.AuthenticateOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, Uid result) + { + PRB.AuthenticateOpResponse response = new PRB.AuthenticateOpResponse(); + if (null != result) + { + response.Uid = MessagesUtil.SerializeMessage(result); + } + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + AuthenticateOpResponse = response + } + }; + } + + protected internal override Uid ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.AuthenticateOpRequest requestMessage) + { + ObjectClass objectClass = new ObjectClass(requestMessage.ObjectClass); + OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + return connectorFacade.Authenticate(objectClass, requestMessage.Username, + MessagesUtil.DeserializeLegacy(requestMessage.Password), operationOptions); + } + } + } + + #endregion + + #region CreateAsyncApiOpImpl + + public class CreateAsyncApiOpImpl : AbstractAPIOperation, ICreateAsyncApiOp + { + public CreateAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual Uid Create(ObjectClass objectClass, ICollection createAttributes, + OperationOptions options) + { + try + { + return CreateAsync(objectClass, createAttributes, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public Task CreateAsync(ObjectClass objectClass, ICollection createAttributes, + OperationOptions options, CancellationToken cancellationToken) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(createAttributes, "createAttributes"); + // check to make sure there's not a uid.. + if (ConnectorAttributeUtil.GetUidAttribute(createAttributes) != null) + { + throw new InvalidAttributeValueException("Parameter 'createAttributes' contains a uid."); + } + + PRB.CreateOpRequest requestBuilder = new + PRB.CreateOpRequest + { + ObjectClass = objectClass.GetObjectClassValue(), + CreateAttributes = MessagesUtil.SerializeLegacy(createAttributes) + }; + + + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return + SubmitRequest(new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, new PRB.OperationRequest { CreateOpRequest = requestBuilder }, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(IdentityConnectors.Framework.Api.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalCreateRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalCreateRequest(context, requestId, completionCallback, builder, CancellationToken); + } + return null; + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + internal class InternalCreateRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalCreateRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.CreateOpResponse GetOperationResponseMessages(PRB.OperationResponse message) + { + if (null != message.CreateOpResponse) + { + return message.CreateOpResponse; + } + Trace.TraceInformation(OperationExpectsMessage, RequestId, "CreateOpResponse"); + return null; + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.CreateOpResponse message) + { + HandleResult(null != message.Uid ? MessagesUtil.DeserializeMessage(message.Uid) : null); + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor(long requestId, + WebSocketConnectionHolder socket, PRB.CreateOpRequest message) + { + return new InternalCreateLocalOperationProcessor(requestId, socket, message); + } + + private class InternalCreateLocalOperationProcessor : AbstractLocalOperationProcessor + { + protected internal InternalCreateLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.CreateOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, Uid result) + { + PRB.CreateOpResponse response = new PRB.CreateOpResponse(); + if (null != result) + { + response.Uid = MessagesUtil.SerializeMessage(result); + } + + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + CreateOpResponse = response + } + }; + } + + protected internal override Uid ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.CreateOpRequest requestMessage) + { + ObjectClass objectClass = new ObjectClass(requestMessage.ObjectClass); + var attributes = MessagesUtil.DeserializeLegacy>(requestMessage.CreateAttributes); + + OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + return connectorFacade.Create(objectClass, CollectionUtil.NewSet(attributes), + operationOptions); + } + } + } + + #endregion + + #region DeleteAsyncApiOpImpl + + public class DeleteAsyncApiOpImpl : AbstractAPIOperation, IDeleteAsyncApiOp + { + public DeleteAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual void Delete(ObjectClass objectClass, Uid uid, OperationOptions options) + { + try + { + DeleteAsync(objectClass, uid, options, CancellationToken.None).Wait(); + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public Task DeleteAsync(ObjectClass objectClass, Uid uid, OperationOptions options, + CancellationToken cancellationToken) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(uid, "uid"); + + PRB.DeleteOpRequest requestBuilder = + new PRB.DeleteOpRequest + { + ObjectClass = objectClass.GetObjectClassValue(), + Uid = MessagesUtil.SerializeMessage(uid) + }; + + + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + return SubmitRequest( + new InternalRequestFactory(ConnectorKey, FacadeKeyFunction, new + PRB.OperationRequest { DeleteOpRequest = requestBuilder }, cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalDeleteRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalDeleteRequest(context, requestId, completionCallback, builder, CancellationToken); + } + return null; + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalDeleteRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalDeleteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.DeleteOpResponse GetOperationResponseMessages(PRB.OperationResponse message) + { + if (null != message.DeleteOpResponse) + { + return message.DeleteOpResponse; + } + Trace.TraceInformation(OperationExpectsMessage, RequestId, "DeleteOpResponse"); + return null; + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.DeleteOpResponse message) + { + HandleResult(null); + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor(long requestId, + WebSocketConnectionHolder socket, PRB.DeleteOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : AbstractLocalOperationProcessor + { + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.DeleteOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, Object result) + { + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + DeleteOpResponse = new PRB.DeleteOpResponse() + } + }; + } + + protected internal override Object ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.DeleteOpRequest requestMessage) + { + ObjectClass objectClass = new ObjectClass(requestMessage.ObjectClass); + Uid uid = MessagesUtil.DeserializeMessage(requestMessage.Uid); + + OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + connectorFacade.Delete(objectClass, uid, operationOptions); + return null; + } + } + } + + #endregion + + #region GetAsyncApiOpImpl + + public class GetAsyncApiOpImpl : AbstractAPIOperation, IGetAsyncApiOp + { + public GetAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual ConnectorObject GetObject(ObjectClass objectClass, Uid uid, OperationOptions options) + { + try + { + return GetObjectAsync(objectClass, uid, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public Task GetObjectAsync(ObjectClass objectClass, Uid uid, OperationOptions options, + CancellationToken cancellationToken) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(uid, "uid"); + + PRB.GetOpRequest requestBuilder = + new PRB.GetOpRequest + { + ObjectClass = objectClass.GetObjectClassValue(), + Uid = MessagesUtil.SerializeMessage(uid) + }; + + + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return + SubmitRequest( + new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, new PRB.OperationRequest { GetOpRequest = requestBuilder }, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.GetOpResponse GetOperationResponseMessages(PRB.OperationResponse message) + { + if (null != message.GetOpResponse) + { + return message.GetOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "GetOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.GetOpResponse message) + { + HandleResult(null != message.ConnectorObject + ? MessagesUtil.DeserializeLegacy(message.ConnectorObject) + : null); + } + } + + // ------- + + public static AbstractLocalOperationProcessor CreateProcessor(long requestId, + WebSocketConnectionHolder socket, PRB.GetOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : + AbstractLocalOperationProcessor + { + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.GetOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, ConnectorObject result) + { + PRB.GetOpResponse response = new PRB.GetOpResponse(); + if (null != result) + { + response.ConnectorObject = MessagesUtil.SerializeLegacy(result); + } + + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + GetOpResponse = response + } + }; + } + + protected internal override ConnectorObject ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.GetOpRequest requestMessage) + { + ObjectClass objectClass = new ObjectClass(requestMessage.ObjectClass); + Uid uid = MessagesUtil.DeserializeMessage(requestMessage.Uid); + + OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + return connectorFacade.GetObject(objectClass, uid, operationOptions); + } + } + } + + #endregion + + #region ResolveUsernameAsyncApiOpImpl + + public class ResolveUsernameAsyncApiOpImpl : AbstractAPIOperation, IResolveUsernameAsyncApiOp + { + public ResolveUsernameAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual Uid ResolveUsername(ObjectClass objectClass, string username, OperationOptions options) + { + try + { + return ResolveUsernameAsync(objectClass, username, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public async Task ResolveUsernameAsync(ObjectClass objectClass, string username, OperationOptions options, + CancellationToken cancellationToken) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(username, "username"); + + PRB.ResolveUsernameOpRequest requestBuilder = + new PRB.ResolveUsernameOpRequest + { + ObjectClass = objectClass.GetObjectClassValue(), + Username = username + }; + + + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return await + SubmitRequest( + new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, new PRB.OperationRequest { ResolveUsernameOpRequest = requestBuilder }, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.ResolveUsernameOpResponse GetOperationResponseMessages( + PRB.OperationResponse message) + { + if (null != message.ResolveUsernameOpResponse) + { + return message.ResolveUsernameOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "ResolveUsernameOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.ResolveUsernameOpResponse message) + { + if (null != message.Uid) + { + HandleResult(MessagesUtil.DeserializeMessage(message.Uid)); + } + else + { + HandleResult(null); + } + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor(long requestId, + WebSocketConnectionHolder socket, PRB.ResolveUsernameOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : + AbstractLocalOperationProcessor + { + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.ResolveUsernameOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, Uid result) + { + PRB.ResolveUsernameOpResponse response = new PRB.ResolveUsernameOpResponse(); + if (null != result) + { + response.Uid = MessagesUtil.SerializeMessage(result); + } + + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + ResolveUsernameOpResponse = response + } + }; + } + + protected internal override Uid ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.ResolveUsernameOpRequest requestMessage) + { + ObjectClass objectClass = new ObjectClass(requestMessage.ObjectClass); + OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + return connectorFacade.ResolveUsername(objectClass, requestMessage.Username, operationOptions); + } + } + } + + #endregion + + #region SchemaAsyncApiOpImpl + + public class SchemaAsyncApiOpImpl : AbstractAPIOperation, ISchemaAsyncApiOp + { + public SchemaAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual Schema Schema() + { + try + { + return SchemaAsync(CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public async Task SchemaAsync(CancellationToken cancellationToken) + { + return await + SubmitRequest(new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, + new PRB.OperationRequest { SchemaOpRequest = new PRB.SchemaOpRequest() }, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.SchemaOpResponse GetOperationResponseMessages(PRB.OperationResponse message) + { + if (null != message.SchemaOpResponse) + { + return message.SchemaOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "SchemaOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.SchemaOpResponse message) + { + if (null != message.Schema) + { + HandleResult(MessagesUtil.DeserializeLegacy(message.Schema)); + } + else + { + HandleResult(null); + } + } + } + + // ------- + + public static AbstractLocalOperationProcessor CreateProcessor(long requestId, + WebSocketConnectionHolder socket, PRB.SchemaOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : AbstractLocalOperationProcessor + { + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.SchemaOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, ByteString result) + { + PRB.SchemaOpResponse response = new PRB.SchemaOpResponse(); + if (null != result) + { + response.Schema = result; + } + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + SchemaOpResponse = response + } + }; + } + + protected internal override ByteString ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.SchemaOpRequest requestMessage) + { + Schema schema = connectorFacade.Schema(); + if (null != schema) + { + return MessagesUtil.SerializeLegacy(schema); + } + return null; + } + } + } + + #endregion + + #region ScriptOnConnectorAsyncApiOpImpl + + public class ScriptOnConnectorAsyncApiOpImpl : AbstractAPIOperation, IScriptOnConnectorAsyncApiOp + { + public ScriptOnConnectorAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual Object RunScriptOnConnector(ScriptContext request, OperationOptions options) + { + try + { + return RunScriptOnConnectorAsync(request, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public Task RunScriptOnConnectorAsync(ScriptContext request, OperationOptions options, + CancellationToken cancellationToken) + { + Assertions.NullCheck(request, "request"); + + PRB.ScriptOnConnectorOpRequest requestBuilder = new PRB.ScriptOnConnectorOpRequest(); + + requestBuilder.ScriptContext = MessagesUtil.SerializeMessage(request); + + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return + SubmitRequest( + new InternalRequestFactory(ConnectorKey, FacadeKeyFunction, + new PRB.OperationRequest { ScriptOnConnectorOpRequest = requestBuilder }, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.ScriptOnConnectorOpResponse GetOperationResponseMessages( + PRB.OperationResponse message) + { + if (null != message.ScriptOnConnectorOpResponse) + { + return message.ScriptOnConnectorOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "ScriptOnConnectorOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.ScriptOnConnectorOpResponse message) + { + if (null != message.Object) + { + HandleResult(MessagesUtil.DeserializeLegacy(message.Object)); + } + else + { + HandleResult(null); + } + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor( + long requestId, WebSocketConnectionHolder socket, PRB.ScriptOnConnectorOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : + AbstractLocalOperationProcessor + { + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.ScriptOnConnectorOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, ByteString result) + { + PRB.ScriptOnConnectorOpResponse response = new PRB.ScriptOnConnectorOpResponse(); + if (null != result) + { + response.Object = result; + } + + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + ScriptOnConnectorOpResponse = response + } + }; + } + + protected internal override ByteString ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.ScriptOnConnectorOpRequest requestMessage) + { + ScriptContext request = MessagesUtil.DeserializeMessage(requestMessage.ScriptContext); + + OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + object result = connectorFacade.RunScriptOnConnector(request, operationOptions); + if (null != result) + { + return MessagesUtil.SerializeLegacy(result); + } + return null; + } + } + } + + #endregion + + #region ScriptOnResourceAsyncApiOpImpl + + public class ScriptOnResourceAsyncApiOpImpl : AbstractAPIOperation, IScriptOnResourceAsyncApiOp + { + public ScriptOnResourceAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual object RunScriptOnResource(ScriptContext request, OperationOptions options) + { + try + { + return RunScriptOnResourceAsync(request, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public async Task RunScriptOnResourceAsync(ScriptContext request, OperationOptions options, + CancellationToken cancellationToken) + { + Assertions.NullCheck(request, "request"); + + PRB.ScriptOnResourceOpRequest requestBuilder = new PRB.ScriptOnResourceOpRequest + { + ScriptContext = MessagesUtil.SerializeMessage(request) + }; + + + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return await + SubmitRequest( + new InternalRequestFactory(ConnectorKey, FacadeKeyFunction, + new PRB.OperationRequest { ScriptOnResourceOpRequest = requestBuilder }, cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.ScriptOnResourceOpResponse GetOperationResponseMessages( + PRB.OperationResponse message) + { + if (null != message.ScriptOnResourceOpResponse) + { + return message.ScriptOnResourceOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "ScriptOnResourceOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.ScriptOnResourceOpResponse message) + { + if (null != message.Object) + { + HandleResult(MessagesUtil.DeserializeLegacy(message.Object)); + } + else + { + HandleResult(null); + } + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor( + long requestId, WebSocketConnectionHolder socket, PRB.ScriptOnResourceOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : + AbstractLocalOperationProcessor + { + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.ScriptOnResourceOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, ByteString result) + { + PRB.ScriptOnResourceOpResponse response = new PRB.ScriptOnResourceOpResponse(); + if (null != result) + { + response.Object = result; + } + + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + ScriptOnResourceOpResponse = response + } + }; + } + + protected internal override ByteString ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.ScriptOnResourceOpRequest requestMessage) + { + ScriptContext request = MessagesUtil.DeserializeMessage(requestMessage.ScriptContext); + + OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + object result = connectorFacade.RunScriptOnResource(request, operationOptions); + if (null != result) + { + return MessagesUtil.SerializeLegacy(result); + } + return null; + } + } + } + + #endregion + + #region SearchAsyncApiOpImpl + + public class SearchAsyncApiOpImpl : AbstractAPIOperation, SearchApiOp + { + public SearchAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual SearchResult Search(ObjectClass objectClass, Filter filter, ResultsHandler handler, + OperationOptions options) + { + try + { + return SearchAsync(objectClass, filter, handler, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public async Task SearchAsync(ObjectClass objectClass, Filter filter, ResultsHandler handler, + OperationOptions options, CancellationToken cancellationToken) + { + Assertions.NullCheck(objectClass, "objectClass"); + if (ObjectClass.ALL.Equals(objectClass)) + { + throw new NotSupportedException("Operation is not allowed on __ALL__ object class"); + } + Assertions.NullCheck(handler, "handler"); + + PRB.SearchOpRequest requestBuilder = new + PRB.SearchOpRequest { ObjectClass = objectClass.GetObjectClassValue() }; + if (filter != null) + { + requestBuilder.Filter = MessagesUtil.SerializeLegacy(filter); + } + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return await + SubmitRequest( + new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, new PRB.OperationRequest { SearchOpRequest = requestBuilder }, handler, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + private readonly ResultsHandler _handler; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + ResultsHandler handler, CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + _handler = handler; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + // This is the context aware request + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, _handler, + CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + private readonly ResultsHandler _handler; + private Int64 _sequence; + private Int64 _expectedResult = -1; + private SearchResult _result; + + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + ResultsHandler handler, CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + _handler = handler; + } + + protected internal override PRB.SearchOpResponse GetOperationResponseMessages(PRB.OperationResponse message) + { + if (null != message.SearchOpResponse) + { + return message.SearchOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "SearchOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.SearchOpResponse message) + { + if (null != message.ConnectorObject) + { + try + { + ConnectorObject co = MessagesUtil.DeserializeMessage(message.ConnectorObject); + + if (!_handler.Handle(co) && !Promise.IsCompleted) + { + HandleError(new ConnectorException("ResultsHandler stopped processing results")); + TryCancelRemote(ConnectionContext, RequestId); + } + } + finally + { + Interlocked.Increment(ref _sequence); + } + } + else + { + if (null != message.Result) + { + _result = MessagesUtil.DeserializeMessage(message.Result); + } + _expectedResult = message.Sequence; + if (_expectedResult == 0 || _sequence == _expectedResult) + { + HandleResult(_result); + } + else + { + Trace.TraceInformation("Response processed before all result has arrived"); + } + } + if (_expectedResult > 0 && _sequence == _expectedResult) + { + HandleResult(_result); + } + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor( + long requestId, WebSocketConnectionHolder socket, PRB.SearchOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : + AbstractLocalOperationProcessor + { + private Int32 _doContinue = 1; + private Int64 _sequence; + + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.SearchOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, PRB.SearchOpResponse response) + { + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + SearchOpResponse = response + } + }; + } + + protected internal override PRB.SearchOpResponse ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.SearchOpRequest requestMessage) + { + ObjectClass objectClass = new ObjectClass(requestMessage.ObjectClass); + Filter filter = null; + if (null != requestMessage.Filter) + { + filter = MessagesUtil.DeserializeLegacy(requestMessage.Filter); + } + + OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + SearchResult searchResult = connectorFacade.Search(objectClass, filter, new ResultsHandler() + { + Handle = connectorObject => + { + if (null != connectorObject) + { + PRB.SearchOpResponse result = + new PRB.SearchOpResponse + { + ConnectorObject = + MessagesUtil.SerializeMessage(connectorObject), + Sequence = Interlocked.Increment(ref _sequence) + }; + + if (TryHandleResult(result)) + { + Trace.TraceInformation("SearchResult sent in sequence:{0}", _sequence); + } + else + { + Trace.TraceInformation("Failed to send response {0}", _sequence); + } + } + return _doContinue == 1; + } + }, operationOptions); + + PRB.SearchOpResponse response = new PRB.SearchOpResponse { Sequence = _sequence }; + if (null != searchResult) + { + response.Result = MessagesUtil.SerializeMessage(searchResult); + } + return response; + } + + protected override bool TryCancel() + { + _doContinue = 0; + return base.TryCancel(); + } + } + } + + #endregion + + #region SyncAsyncApiOpImpl + + public class SyncAsyncApiOpImpl : AbstractAPIOperation, SyncApiOp + { + public SyncAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual SyncToken GetLatestSyncToken(ObjectClass objectClass) + { + try + { + return GetLatestSyncTokenAsync(objectClass, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public virtual SyncToken Sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler, + OperationOptions options) + { + try + { + return SyncAsync(objectClass, token, handler, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public async Task GetLatestSyncTokenAsync(ObjectClass objectClass, + CancellationToken cancellationToken) + { + Assertions.NullCheck(objectClass, "objectClass"); + return await + SubmitRequest(new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, + new PRB.OperationRequest + { + SyncOpRequest = + new PRB.SyncOpRequest + { + LatestSyncToken = new PRB.SyncOpRequest.Types.LatestSyncToken + { + ObjectClass = objectClass.GetObjectClassValue() + } + } + }, null, cancellationToken)); + } + + public Task SyncAsync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler, + OperationOptions options, CancellationToken cancellationToken) + { + Assertions.NullCheck(objectClass, "objectClass"); + Assertions.NullCheck(handler, "handler"); + PRB.SyncOpRequest.Types.Sync requestBuilder = + new PRB.SyncOpRequest.Types.Sync { ObjectClass = objectClass.GetObjectClassValue() }; + if (token != null) + { + requestBuilder.Token = MessagesUtil.SerializeMessage(token); + } + + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return + SubmitRequest(new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, + new PRB.OperationRequest + { + SyncOpRequest = new PRB.SyncOpRequest { Sync = requestBuilder } + }, handler, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + private readonly SyncResultsHandler _handler; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + SyncResultsHandler handler, CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + _handler = handler; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, _handler, + CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + private readonly SyncResultsHandler _handler; + private Int64 _sequence; + private Int64 _expectedResult = -1; + private SyncToken _result; + + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + SyncResultsHandler handler, CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + _handler = handler; + } + + protected internal override PRB.SyncOpResponse GetOperationResponseMessages(PRB.OperationResponse message) + { + if (null != message.SyncOpResponse) + { + return message.SyncOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "SyncOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.SyncOpResponse message) + { + if (null != message.LatestSyncToken) + { + if (null != message.LatestSyncToken.SyncToken) + { + HandleResult(MessagesUtil.DeserializeMessage(message.LatestSyncToken.SyncToken)); + } + else + { + HandleResult(null); + } + } + else if (null != message.Sync) + { + Trace.TraceInformation("SyncOp Response received in sequence:{0} of {1}", message.Sync.Sequence, + _sequence); + if (null != message.Sync.SyncDelta) + { + try + { + SyncDelta delta = + MessagesUtil.DeserializeMessage( + message.Sync.SyncDelta); + + if (!_handler.Handle(delta) && !Promise.IsCompleted) + { + HandleError(new ConnectorException("SyncResultsHandler stopped processing results")); + TryCancelRemote(ConnectionContext, RequestId); + } + } + finally + { + Interlocked.Increment(ref _sequence); + } + } + else + { + if (null != message.Sync.SyncToken) + { + _result = MessagesUtil.DeserializeMessage(message.Sync.SyncToken); + } + _expectedResult = message.Sync.Sequence; + if (_expectedResult == 0 || _sequence == _expectedResult) + { + HandleResult(_result); + } + else + { + Trace.TraceInformation("Response processed before all result has arrived"); + } + } + if (_expectedResult > 0 && _sequence == _expectedResult) + { + HandleResult(_result); + } + } + else + { + Trace.TraceInformation("Invalid SyncOpResponse Response:{0}", RequestId); + } + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor( + long requestId, WebSocketConnectionHolder socket, PRB.SyncOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : + AbstractLocalOperationProcessor + { + private Int32 _doContinue = 1; + private Int64 _sequence; + + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.SyncOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, PRB.SyncOpResponse response) + { + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + SyncOpResponse = response + } + }; + } + + protected internal override PRB.SyncOpResponse ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.SyncOpRequest requestMessage) + { + if (null != requestMessage.LatestSyncToken) + { + ObjectClass objectClass = new ObjectClass(requestMessage.LatestSyncToken.ObjectClass); + SyncToken token = connectorFacade.GetLatestSyncToken(objectClass); + + // Enable returnNullTest + PRB.SyncOpResponse.Types.LatestSyncToken builder = new + PRB.SyncOpResponse.Types.LatestSyncToken(); + if (null != token) + { + builder.SyncToken = MessagesUtil.SerializeMessage(token); + } + return new PRB.SyncOpResponse { LatestSyncToken = builder }; + } + else if (null != requestMessage.Sync) + { + ObjectClass objectClass = new ObjectClass(requestMessage.Sync.ObjectClass); + SyncToken token = MessagesUtil.DeserializeMessage(requestMessage.Sync.Token); + + OperationOptions operationOptions = null; + if (null != requestMessage.Sync.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Sync.Options); + } + + SyncToken syncResult = connectorFacade.Sync(objectClass, token, new SyncResultsHandler() + { + Handle = delta => + { + if (null != delta) + { + PRB.SyncOpResponse result = + new PRB.SyncOpResponse + { + Sync = + new PRB.SyncOpResponse.Types.Sync + { + SyncDelta = MessagesUtil.SerializeMessage(delta), + Sequence = Interlocked.Increment(ref _sequence) + } + }; + + if (TryHandleResult(result)) + { + Trace.TraceInformation("SyncResult sent in sequence:{0}", _sequence); + } + else + { + Trace.TraceInformation("Failed to send response {0}", _sequence); + } + } + return _doContinue == 1; + } + }, operationOptions); + + PRB.SyncOpResponse.Types.Sync builder = + new PRB.SyncOpResponse.Types.Sync { Sequence = _sequence }; + if (null != syncResult) + { + builder.SyncToken = MessagesUtil.SerializeMessage(syncResult); + } + + return new PRB.SyncOpResponse { Sync = builder }; + } + else + { + Trace.TraceInformation("Invalid SyncOpRequest Request:{0}", RequestId); + } + return null; + } + + + protected override bool TryCancel() + { + _doContinue = 0; + return base.TryCancel(); + } + } + } + + #endregion + + #region ConnectorEventSubscriptionApiOpImpl + + #endregion + + #region TestAsyncApiOpImpl + + public class TestAsyncApiOpImpl : AbstractAPIOperation, ITestAsyncApiOp + { + public TestAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual void Test() + { + try + { + TestAsync(CancellationToken.None).Wait(); + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public async Task TestAsync(CancellationToken cancellationToken) + { + await SubmitRequest(new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, new PRB.OperationRequest { TestOpRequest = new PRB.TestOpRequest() }, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.TestOpResponse GetOperationResponseMessages(PRB.OperationResponse message) + { + if (null != message.TestOpResponse) + { + return message.TestOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "TestOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.TestOpResponse message) + { + HandleResult(null); + } + + public override string ToString() + { + return "Test Request {}" + RequestId; + } + } + + // ---- + public static AbstractLocalOperationProcessor CreateProcessor(long requestId, + WebSocketConnectionHolder socket, PRB.TestOpRequest message) + { + return new TestLocalOperationProcessor(requestId, socket, message); + } + + private class TestLocalOperationProcessor : AbstractLocalOperationProcessor + { + protected internal TestLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.TestOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, Object response) + { + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + TestOpResponse = new PRB.TestOpResponse() + } + }; + } + + protected internal override Object ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.TestOpRequest requestMessage) + { + connectorFacade.Test(); + return null; + } + } + } + + #endregion + + #region UpdateAsyncApiOpImpl + + public class UpdateAsyncApiOpImpl : AbstractAPIOperation, IUpdateAsyncApiOp + { + public UpdateAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual Uid Update(ObjectClass objectClass, Uid uid, ICollection replaceAttributes, + OperationOptions options) + { + try + { + return UpdateAsync(objectClass, uid, replaceAttributes, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public virtual Uid AddAttributeValues(ObjectClass objectClass, Uid uid, + ICollection valuesToAdd, OperationOptions options) + { + try + { + return AddAttributeValuesAsync(objectClass, uid, valuesToAdd, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public virtual Uid RemoveAttributeValues(ObjectClass objectClass, Uid uid, + ICollection valuesToRemove, OperationOptions options) + { + try + { + return + RemoveAttributeValuesAsync(objectClass, uid, valuesToRemove, options, CancellationToken.None).Result; + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public async Task UpdateAsync(ObjectClass objectClass, Uid uid, + ICollection replaceAttributes, OperationOptions options, + CancellationToken cancellationToken) + { + return + await + DoUpdate(objectClass, uid, PRB.UpdateOpRequest.Types.UpdateType.REPLACE, replaceAttributes, options, + cancellationToken); + } + + public async Task AddAttributeValuesAsync(ObjectClass objectClass, Uid uid, + ICollection valuesToAdd, OperationOptions options, CancellationToken cancellationToken) + { + return await DoUpdate(objectClass, uid, PRB.UpdateOpRequest.Types.UpdateType.ADD, valuesToAdd, options, + cancellationToken); + } + + public async Task RemoveAttributeValuesAsync(ObjectClass objectClass, Uid uid, + ICollection valuesToRemove, OperationOptions options, + CancellationToken cancellationToken) + { + return + await DoUpdate(objectClass, uid, PRB.UpdateOpRequest.Types.UpdateType.REMOVE, valuesToRemove, options, + cancellationToken); + } + + public async Task DoUpdate(ObjectClass objectClass, Uid uid, + PRB.UpdateOpRequest.Types.UpdateType updateType, + ICollection replaceAttributes, OperationOptions options, + CancellationToken cancellationToken) + { + UpdateImpl.ValidateInput(objectClass, uid, replaceAttributes, + !PRB.UpdateOpRequest.Types.UpdateType.REPLACE.Equals(updateType)); + PRB.UpdateOpRequest requestBuilder = + new PRB.UpdateOpRequest + { + ObjectClass = objectClass.GetObjectClassValue(), + Uid = MessagesUtil.SerializeMessage(uid), + UpdateType = updateType, + ReplaceAttributes = MessagesUtil.SerializeLegacy(replaceAttributes) + }; + + + if (options != null) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return await + SubmitRequest(new InternalRequestFactory(ConnectorKey, + FacadeKeyFunction, new PRB.OperationRequest { UpdateOpRequest = requestBuilder }, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.UpdateOpResponse GetOperationResponseMessages(PRB.OperationResponse message) + { + if (null != message.UpdateOpResponse) + { + return message.UpdateOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "UpdateOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.UpdateOpResponse message) + { + if (null != message.Uid) + { + HandleResult(MessagesUtil.DeserializeMessage(message.Uid)); + } + else + { + HandleResult(null); + } + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor(long requestId, + WebSocketConnectionHolder socket, PRB.UpdateOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : AbstractLocalOperationProcessor + { + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.UpdateOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, Uid result) + { + PRB.UpdateOpResponse response = new PRB.UpdateOpResponse(); + if (null != result) + { + response.Uid = MessagesUtil.SerializeMessage(result); + } + + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + UpdateOpResponse = response + } + }; + } + + protected internal override Uid ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.UpdateOpRequest requestMessage) + { + ObjectClass objectClass = new ObjectClass(requestMessage.ObjectClass); + Uid uid = MessagesUtil.DeserializeMessage(requestMessage.Uid); + + var attributes = MessagesUtil.DeserializeLegacy>(requestMessage.ReplaceAttributes); + + OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + + switch (requestMessage.UpdateType) + { + case PRB.UpdateOpRequest.Types.UpdateType.REPLACE: + return connectorFacade.Update(objectClass, uid, + CollectionUtil.NewSet(attributes), operationOptions); + case PRB.UpdateOpRequest.Types.UpdateType.ADD: + return connectorFacade.AddAttributeValues(objectClass, uid, + CollectionUtil.NewSet(attributes), operationOptions); + case PRB.UpdateOpRequest.Types.UpdateType.REMOVE: + return connectorFacade.RemoveAttributeValues(objectClass, uid, + CollectionUtil.NewSet(attributes), operationOptions); + default: + Trace.TraceInformation("Invalid UpdateOpRequest#UpdateType Request:{0}", RequestId); + break; + } + + return null; + } + } + } + + #endregion + + #region ValidateAsyncApiOpImpl + + public class ValidateAsyncApiOpImpl : AbstractAPIOperation, IValidateAsyncApiOp + { + public ValidateAsyncApiOpImpl( + IRequestDistributor + remoteConnection, API.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual void Validate() + { + try + { + ValidateAsync(CancellationToken.None).Wait(); + } + catch (AggregateException e) + { + throw e.InnerException; + } + } + + public async Task ValidateAsync(CancellationToken cancellationToken) + { + await SubmitRequest( + new InternalRequestFactory(ConnectorKey, FacadeKeyFunction, + new PRB.OperationRequest { ValidateOpRequest = new PRB.ValidateOpRequest() }, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly PRB.OperationRequest _operationRequest; + + public InternalRequestFactory(API.ConnectorKey connectorKey, + Func facadeKeyFunction, PRB.OperationRequest operationRequest, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + PRB.RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, CancellationToken); + } + else + { + return null; + } + } + + protected internal override PRB.OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, PRB.RPCRequest requestBuilder, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + } + + protected internal override PRB.ValidateOpResponse GetOperationResponseMessages( + PRB.OperationResponse message) + { + if (null != message.ValidateOpResponse) + { + return message.ValidateOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "ValidateOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + PRB.ValidateOpResponse message) + { + HandleResult(null); + } + } + + // ---- + + public static AbstractLocalOperationProcessor CreateProcessor(long requestId, + WebSocketConnectionHolder socket, PRB.ValidateOpRequest message) + { + return new ValidateLocalOperationProcessor(requestId, socket, message); + } + + private class ValidateLocalOperationProcessor : AbstractLocalOperationProcessor + { + protected internal ValidateLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + PRB.ValidateOpRequest message) + : base(requestId, socket, message) + { + } + + protected override PRB.RPCResponse CreateOperationResponse( + RemoteOperationContext remoteContext, Object result) + { + return new + PRB.RPCResponse + { + OperationResponse = new PRB.OperationResponse + { + ValidateOpResponse = new PRB.ValidateOpResponse() + } + }; + } + + protected internal override Object ExecuteOperation(API.ConnectorFacade connectorFacade, + PRB.ValidateOpRequest requestMessage) + { + connectorFacade.Validate(); + return null; + } + } + } + + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/Client.cs b/dotnet/framework/FrameworkServer/Client.cs new file mode 100755 index 00000000..57104d96 --- /dev/null +++ b/dotnet/framework/FrameworkServer/Client.cs @@ -0,0 +1,598 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Org.ForgeRock.OpenICF.Common.ProtoBuf; +using Org.ForgeRock.OpenICF.Common.RPC; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Exceptions; + +namespace Org.ForgeRock.OpenICF.Framework.Remote +{ + + #region ClientRemoteConnectorInfoManager + + public class ClientRemoteConnectorInfoManager : ConnectionPrincipal, IRemoteConnectorInfoManager + { + private readonly RemoteDelegatingAsyncConnectorInfoManager _delegatingAsyncConnectorInfoManager; + + private readonly WebSocketWrapper[] _connections = new WebSocketWrapper[2]; + + private readonly RemoteWSFrameworkConnectionInfo _info; + + private readonly Timer _timer; + + public ClientRemoteConnectorInfoManager(RemoteWSFrameworkConnectionInfo info, + IMessageListener listener, + ConcurrentDictionary globalConnectionGroups) + : base(listener, globalConnectionGroups) + { + _delegatingAsyncConnectorInfoManager = + new RemoteDelegatingAsyncConnectorInfoManager(this); + + _info = info; + _timer = new Timer(state => + { + WebSocketConnectionHolder[] connections = state as WebSocketConnectionHolder[]; + if (null != connections) + { + for (int i = 0; i < 2; i++) + { + if (connections[i] == null || !connections[i].Operational) + { + WebSocketWrapper vr = WebSocketWrapper.Create(_info, listener, Handshake); + vr.ConnectAsync().ContinueWith((result, o) => + { + if (result.IsCompleted) + { + connections[(int)o] = result.Result; + } + else if (result.IsFaulted) + { + TraceUtil.TraceException("Failed to establish connection", result.Exception); + } + }, i); + } + } + } + }, _connections, TimeSpan.FromSeconds(1), TimeSpan.FromMinutes(5)); + } + + protected override void DoClose() + { + _timer.Dispose(); + foreach (var connection in _connections) + { + connection.Dispose(); + } + } + + public IAsyncConnectorInfoManager AsyncConnectorInfoManager + { + get { return _delegatingAsyncConnectorInfoManager; } + } + + public IRequestDistributor + RequestDistributor + { + get { return this; } + } + + protected override void OnNewWebSocketConnectionGroup(WebSocketConnectionGroup connectionGroup) + { + Trace.TraceInformation("Activating new ConnectionGroup {0}:{1}", Identity.Name, + connectionGroup.RemoteSessionId); + _delegatingAsyncConnectorInfoManager.OnAddAsyncConnectorInfoManager(connectionGroup); + } + + private sealed class RemoteDelegatingAsyncConnectorInfoManager : DelegatingAsyncConnectorInfoManager + { + private readonly ClientRemoteConnectorInfoManager _parent; + + public RemoteDelegatingAsyncConnectorInfoManager(ClientRemoteConnectorInfoManager parent) + : base(true) + { + _parent = parent; + } + + protected override + IRequestDistributor + MessageDistributor + { + get { return _parent.RequestDistributor; } + } + + protected override void DoClose() + { + _parent.Dispose(); + } + + protected override IReadOnlyCollection Delegates + { + get { return _parent.ConnectionGroups.Values as IReadOnlyCollection; } + } + } + } + + #endregion + + #region ConnectionManagerConfig + + public class ConnectionManagerConfig + { + } + + #endregion + + #region RemoteConnectionInfoManagerFactory + + public class RemoteConnectionInfoManagerFactory : IDisposable + { + private readonly IMessageListener + _messageListener; + + protected readonly ConnectionManagerConfig managerConfig; + + protected readonly ConcurrentDictionary connectionGroups = + new ConcurrentDictionary(); + + private readonly ConcurrentDictionary + registry = + new ConcurrentDictionary(); + + private readonly Timer _timer; + + internal RemoteConnectionInfoManagerFactory( + IMessageListener + messageListener, + ConnectionManagerConfig managerConfig) + { + this._messageListener = messageListener; + this.managerConfig = managerConfig; + + + _timer = new Timer(state => + { + if (Running) + { + foreach (var connectionGroup in connectionGroups.Values) + { + Trace.TraceInformation("Check ConnectionGroup:{0} - operational={1}", connectionGroup.RemoteSessionId + , connectionGroup.Operational); + connectionGroup.CheckIsActive(); + } + } + }, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(4)); + } + + public IRemoteConnectorInfoManager Connect(RemoteWSFrameworkConnectionInfo info) + { + if (Running) + { + ClientRemoteConnectorInfoManager manager; + registry.TryGetValue(info, out manager); + if (null == manager) + { + lock (registry) + { + registry.TryGetValue(info, out manager); + if (null == manager) + { + manager = new ClientRemoteConnectorInfoManager(info, MessageListener, connectionGroups); + manager.Disposed += (sender, args) => + { + ClientRemoteConnectorInfoManager ignore; + registry.TryRemove(info, out ignore); + }; + + registry.TryAdd(info, manager); + if (!Running && registry.TryRemove(info, out manager)) + { + manager.Dispose(); + throw new InvalidOperationException("RemoteConnectionInfoManagerFactory is shut down"); + } + } + } + } + return manager; + } + throw new InvalidOperationException("RemoteConnectionInfoManagerFactory is shut down"); + } + + + protected internal void doClose() + { + _timer.Dispose(); + foreach (var clientRemoteConnectorInfoManager in registry.Values) + { + clientRemoteConnectorInfoManager.Dispose(); + } + } + + protected internal virtual + IMessageListener + MessageListener + { + get { return _messageListener; } + } + + protected internal virtual ConnectionManagerConfig ManagerConfig + { + get { return managerConfig; } + } + + private Int32 _isRunning = 1; + + public virtual bool Running + { + get { return _isRunning != 0; } + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _isRunning, 0, 1) == 0) + { + doClose(); + // Notify CloseListeners + /* + CloseListener closeListener; + while ((closeListener = closeListeners.RemoveFirst()) != null) + { + invokeCloseListener(closeListener); + }*/ + } + } + } + + #endregion + + #region IRemoteConnectorInfoManager + + public interface IRemoteConnectorInfoManager : IDisposable + { + IAsyncConnectorInfoManager AsyncConnectorInfoManager { get; } + + IRequestDistributor + RequestDistributor { get; } + } + + #endregion + + #region RemoteWSFrameworkConnectionInfo + + public class RemoteWSFrameworkConnectionInfo + { + public const string OPENICF_PROTOCOL = "v1.openicf.forgerock.org"; + + /// + /// The host to use as proxy. + /// + public const string PROXY_HOST = "http.proxyHost"; + + /// + /// The port to use for the proxy. + /// + public const string PROXY_PORT = "http.proxyPort"; + + private bool secure = false; + private Uri remoteURI; + private string encoding = "UTF-8"; + private long heartbeatInterval; + + private string proxyHost; + private int proxyPort = -1; + private string proxyPrincipal = null; + private GuardedString proxyPassword = null; + + + public Uri RemoteUri + { + set + { + Assertions.NullCheck(value, "remoteURI"); + + if ("https".Equals(value.Scheme, StringComparison.CurrentCultureIgnoreCase) || + "wss".Equals(value.Scheme, StringComparison.CurrentCultureIgnoreCase)) + { + int port = value.Port > 0 ? value.Port : 443; + secure = true; + + remoteURI = new UriBuilder(value) + { + Scheme = "wss", + Port = port + }.Uri; + } + else if ("http".Equals(value.Scheme, StringComparison.CurrentCultureIgnoreCase) || + "ws".Equals(value.Scheme, StringComparison.CurrentCultureIgnoreCase)) + { + int port = value.Port > 0 ? value.Port : 80; + remoteURI = new UriBuilder(value) + { + Scheme = "ws", + Port = port + }.Uri; + secure = false; + } + else + { + throw new ArgumentException("Unsupported protocol:" + value); + } + } + get { return remoteURI; } + } + + public virtual string Principal { get; set; } + + public virtual GuardedString Password { get; set; } + + public virtual bool Secure + { + get { return secure; } + } + + /// + /// Returns the heartbeat interval (in seconds) to use for the connection. A value + /// of zero means default 60 seconds timeout. + /// + /// the heartbeat interval (in seconds) to use for the connection. + public virtual long HeartbeatInterval + { + get { return heartbeatInterval; } + } + } + + #endregion + + #region WebSocketWrapper + + public class WebSocketWrapper : WebSocketConnectionHolder + { + private const int ReceiveChunkSize = 4096; + private const int SendChunkSize = 4096; + + private readonly ClientWebSocket _ws; + private readonly TaskCompletionSource _connectPromise; + private readonly RemoteWSFrameworkConnectionInfo _info; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly CancellationToken _cancellationToken; + + private readonly IMessageListener + _messageListener; + + private readonly Func _handshaker; + + protected WebSocketWrapper(RemoteWSFrameworkConnectionInfo info, + IMessageListener + messageListener, + Func handshaker) + { + _messageListener = messageListener; + _handshaker = handshaker; + _ws = new ClientWebSocket(); + _connectPromise = new TaskCompletionSource(); + _ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(20); + _ws.Options.AddSubProtocol(RemoteWSFrameworkConnectionInfo.OPENICF_PROTOCOL); + string enc = + Convert.ToBase64String( + Encoding.GetEncoding("iso-8859-1") + .GetBytes(info.Principal + ":" + + IdentityConnectors.Common.Security.SecurityUtil.Decrypt(info.Password))); + _ws.Options.SetRequestHeader("Authorization", "Basic " + enc); + _info = info; + _cancellationToken = _cancellationTokenSource.Token; + } + + /// + /// Creates a new instance. + /// + /// The URI of the WebSocket server. + /// + /// + /// + public static WebSocketWrapper Create(RemoteWSFrameworkConnectionInfo info, + IMessageListener + messageListener, + Func handshaker) + { + return new WebSocketWrapper(info, messageListener, handshaker); + } + + + private async void SendMessageAsync(byte[] message) + { + if (_ws.State != WebSocketState.Open) + { + throw new Exception("Connection is not open."); + } + + await WriteMessageAsync(message, WebSocketMessageType.Binary); + } + + private async void SendMessageAsync(string message) + { + if (_ws.State != WebSocketState.Open) + { + throw new Exception("Connection is not open."); + } + + var messageBuffer = Encoding.UTF8.GetBytes(message); + await WriteMessageAsync(messageBuffer, WebSocketMessageType.Text); + } + + private static Int32 count = 0; + + public async Task ConnectAsync() + { + Trace.TraceInformation("Client make attempt to connect {0}", count++); + _ws.ConnectAsync(_info.RemoteUri, _cancellationToken).ContinueWith(task => + { + if (task.IsCompleted) + { + StartListen(); + RunInTask(WriteMessageAsync); + RunInTask(() => _messageListener.OnConnect(this)); + } + else + { + _connectPromise.SetException(task.Exception ?? new Exception("Failed to Connect Remote Server")); + } + }, CancellationToken.None).ConfigureAwait(false); + return await _connectPromise.Task; + } + + private async void StartListen() + { + var buffer = new byte[ReceiveChunkSize]; + try + { + while (_ws.State == WebSocketState.Open) + { + StringBuilder stringResult = null; + MemoryStream binaryResult = null; + + WebSocketReceiveResult result; + do + { + result = await _ws.ReceiveAsync(new ArraySegment(buffer), _cancellationToken); + + if (result.MessageType == WebSocketMessageType.Close) + { + await + _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); + RunInTask(() => _messageListener.OnClose(this, 1000, null)); + } + else if (result.MessageType == WebSocketMessageType.Binary) + { + if (null == binaryResult) + { + binaryResult = new MemoryStream(); + } + binaryResult.Write(buffer, 0, result.Count); + } + else + { + var str = Encoding.UTF8.GetString(buffer, 0, result.Count); + if (null == stringResult) + { + stringResult = new StringBuilder(); + } + stringResult.Append(str); + } + } while (!result.EndOfMessage); + + if (result.MessageType == WebSocketMessageType.Binary) + { + if (binaryResult != null) + RunInTask(() => _messageListener.OnMessage(this, binaryResult.ToArray())); + } + else + { + if (stringResult != null) + RunInTask(() => _messageListener.OnMessage(this, stringResult.ToString())); + } + } + } + catch (Exception e) + { + TraceUtil.TraceException("Client processing exception", e); + RunInTask(() => _messageListener.OnError(e)); + } + finally + { + _ws.Dispose(); + } + } + + private static void RunInTask(Action action) + { + Task.Factory.StartNew(action); + } + + protected override async Task WriteMessageAsync(byte[] entry, WebSocketMessageType messageType) + { + var messageBuffer = entry; + var messagesCount = (int)Math.Ceiling((double)messageBuffer.Length / SendChunkSize); + + for (var i = 0; i < messagesCount; i++) + { + var offset = (SendChunkSize * i); + var count = SendChunkSize; + var lastMessage = ((i + 1) == messagesCount); + + if ((count * (i + 1)) > messageBuffer.Length) + { + count = messageBuffer.Length - offset; + } + + await + _ws.SendAsync(new ArraySegment(messageBuffer, offset, count), messageType, + lastMessage, _cancellationToken); + } + } + + private RemoteOperationContext _context; + + protected override void Handshake(HandshakeMessage message) + { + _context = _handshaker(this, message); + + if (null != _context) + { + _connectPromise.TrySetResult(this); + } + else + { + _connectPromise.TrySetException(new ConnectorException( + "Failed Application HandShake")); + TryClose(); + } + } + + protected override void TryClose() + { + } + + public override bool Operational + { + get { return _ws.State == WebSocketState.Open; } + } + + public override RemoteOperationContext RemoteConnectionContext + { + get { return _context; } + } + } + + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/ConnectorEventSubscriptionApiOpImpl.cs b/dotnet/framework/FrameworkServer/ConnectorEventSubscriptionApiOpImpl.cs new file mode 100755 index 00000000..49c55f43 --- /dev/null +++ b/dotnet/framework/FrameworkServer/ConnectorEventSubscriptionApiOpImpl.cs @@ -0,0 +1,649 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Google.Protobuf; +using Org.ForgeRock.OpenICF.Common.ProtoBuf; +using Org.ForgeRock.OpenICF.Common.RPC; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using OBJ = Org.IdentityConnectors.Framework.Common.Objects; + +namespace Org.ForgeRock.OpenICF.Framework.Remote +{ + + #region ConnectorEventSubscriptionApiOpImpl + + public class ConnectorEventSubscriptionApiOpImpl : AbstractAPIOperation, IConnectorEventSubscriptionApiOp + { + public ConnectorEventSubscriptionApiOpImpl( + IRequestDistributor + remoteConnection, Org.IdentityConnectors.Framework.Api.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual OBJ.ISubscription Subscribe(OBJ.ObjectClass objectClass, Filter eventFilter, + IObserver handler, + OBJ.OperationOptions operationOptions) + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + Task promise = + TrySubscribe(objectClass, eventFilter, handler, operationOptions, CancellationToken.None); + + promise.ContinueWith((task, state) => + { + var observer = state as IObserver; + if (task.IsFaulted) + { + if (null != observer) + { + if (task.Exception != null) observer.OnError(task.Exception); + } + } + else if (task.IsCanceled) + { + //Ignore + } + else + { + if (null != observer) + { + observer.OnCompleted(); + } + } + }, handler, cancellationTokenSource.Token); + + return new SubscriptionImpl(promise, cancellationTokenSource); + } + + + public virtual Task TrySubscribe(OBJ.ObjectClass objectClass, Filter eventFilter, + IObserver handler, + OBJ.OperationOptions options, + CancellationToken cancellationToken) + { + Assertions.NullCheck(objectClass, "objectClass"); + Assertions.NullCheck(handler, "handler"); + ConnectorEventSubscriptionOpRequest requestBuilder = + new ConnectorEventSubscriptionOpRequest {ObjectClass = objectClass.GetObjectClassValue()}; + if (eventFilter != null) + { + requestBuilder.EventFilter = MessagesUtil.SerializeLegacy(eventFilter); + } + + if (options != null && options.Options.Any()) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return + SubmitRequest( + new InternalRequestFactory(ConnectorKey, FacadeKeyFunction, + new OperationRequest + { + ConnectorEventSubscriptionOpRequest = requestBuilder + }, handler, + cancellationToken)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly OperationRequest _operationRequest; + private readonly IObserver _handler; + + public InternalRequestFactory(Org.IdentityConnectors.Framework.Api.ConnectorKey connectorKey, + Func facadeKeyFunction, OperationRequest operationRequest, + IObserver handler, + CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + _handler = handler; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, _handler, + CancellationToken); + } + return null; + + } + + protected internal override OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + private readonly IObserver _handler; + private Int32 _confirmed = 0; + + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, RPCRequest requestBuilder, + IObserver handler, + CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + _handler = handler; + } + + protected internal override ConnectorEventSubscriptionOpResponse GetOperationResponseMessages( + OperationResponse message) + { + if (null != message.ConnectorEventSubscriptionOpResponse) + { + return message.ConnectorEventSubscriptionOpResponse; + } + Trace.TraceInformation(OperationExpectsMessage, RequestId, "ConnectorEventSubscriptionOpResponse"); + return null; + + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + ConnectorEventSubscriptionOpResponse message) + { + if (null != _handler && null != message.ConnectorObject) + { + Org.IdentityConnectors.Framework.Common.Objects.ConnectorObject delta = + MessagesUtil.DeserializeMessage + (message.ConnectorObject); + try + { + _handler.OnNext(delta); + } + catch (Exception) + { + if (!Promise.IsCompleted) + { + HandleError(new ConnectorException("ResultsHandler stopped processing results")); + TryCancelRemote(ConnectionContext, RequestId); + } + } + } + else if (message.Completed && message.Completed) + { + HandleResult(null); + Trace.TraceInformation("Subscription is completed"); + } + else if (Interlocked.CompareExchange(ref _confirmed, 0, 1) == 0) + { + Trace.TraceInformation("Subscription has been made successfully on remote side"); + } + } + } + + // ---- + + public static + AbstractLocalOperationProcessor + CreateProcessor(long requestId, WebSocketConnectionHolder socket, + ConnectorEventSubscriptionOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : + AbstractLocalOperationProcessor, + IObserver + { + private OBJ.ISubscription _subscription; + + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + ConnectorEventSubscriptionOpRequest message) + : base(requestId, socket, message) + { + } + + protected override RPCResponse CreateOperationResponse(RemoteOperationContext remoteContext, + ConnectorEventSubscriptionOpResponse result) + { + return + new RPCResponse + { + OperationResponse = new OperationResponse + { + ConnectorEventSubscriptionOpResponse = result + } + }; + } + + public override void Execute(ConnectorFacade connectorFacade) + { + try + { + TryHandleResult(ExecuteOperation(connectorFacade, _requestMessage)); + } + catch (Exception error) + { + HandleError(error); + } + } + + protected internal override ConnectorEventSubscriptionOpResponse ExecuteOperation( + ConnectorFacade connectorFacade, ConnectorEventSubscriptionOpRequest requestMessage) + { + OBJ.ObjectClass objectClass = new OBJ.ObjectClass(requestMessage.ObjectClass); + + Filter token = null; + if (null != requestMessage.EventFilter) + { + token = MessagesUtil.DeserializeLegacy(requestMessage.EventFilter); + } + + OBJ.OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + + _subscription = connectorFacade.Subscribe(objectClass, token, this, operationOptions); + + return new ConnectorEventSubscriptionOpResponse(); + } + + + protected override bool TryCancel() + { + _subscription.Dispose(); + return base.TryCancel(); + } + + public void OnNext(Org.IdentityConnectors.Framework.Common.Objects.ConnectorObject value) + { + if (null != value) + { + if (!TryHandleResult(new + ConnectorEventSubscriptionOpResponse + { + ConnectorObject = MessagesUtil.SerializeMessage(value) + })) + { + OnError(new Exception("Failed to Handle OnNext event.")); + } + } + } + + public void OnError(Exception error) + { + try + { + byte[] responseMessage = MessagesUtil.CreateErrorResponse(RequestId, error).ToByteArray(); + TrySendBytes(responseMessage, true); + } + catch (Exception t) + { + Trace.TraceInformation + ("Operation encountered an exception and failed to send the exception response {0}", t.Message); + } + } + + public void OnCompleted() + { + TryHandleResult(new ConnectorEventSubscriptionOpResponse {Completed = true}); + } + } + } + + #endregion + + #region SyncEventSubscriptionApiOpImpl + + public class SyncEventSubscriptionApiOpImpl : AbstractAPIOperation, ISyncEventSubscriptionApiOp + { + public SyncEventSubscriptionApiOpImpl( + IRequestDistributor + remoteConnection, Org.IdentityConnectors.Framework.Api.ConnectorKey connectorKey, + Func facadeKeyFunction, long timeout) + : base(remoteConnection, connectorKey, facadeKeyFunction, timeout) + { + } + + public virtual OBJ.ISubscription Subscribe(OBJ.ObjectClass objectClass, OBJ.SyncToken eventFilter, + IObserver handler, OBJ.OperationOptions operationOptions) + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + Task promise = + TrySubscribe(objectClass, eventFilter, handler, operationOptions); + + promise.ContinueWith((task, state) => + { + var observer = state as IObserver; + if (task.IsFaulted) + { + if (null != observer) + { + if (task.Exception != null) observer.OnError(task.Exception); + } + } + else if (task.IsCanceled) + { + //Ignore + } + else + { + if (null != observer) + { + observer.OnCompleted(); + } + } + }, handler, cancellationTokenSource.Token); + + return new SubscriptionImpl(promise, cancellationTokenSource); + } + + + public virtual Task TrySubscribe(OBJ.ObjectClass objectClass, OBJ.SyncToken eventFilter, + IObserver handler, OBJ.OperationOptions options) + { + Assertions.NullCheck(objectClass, "objectClass"); + Assertions.NullCheck(handler, "handler"); + SyncEventSubscriptionOpRequest requestBuilder = new + SyncEventSubscriptionOpRequest {ObjectClass = objectClass.GetObjectClassValue()}; + if (eventFilter != null) + { + requestBuilder.Token = + MessagesUtil.SerializeMessage(eventFilter); + } + + if (options != null && options.Options.Any()) + { + requestBuilder.Options = MessagesUtil.SerializeLegacy(options); + } + + return + SubmitRequest( + new InternalRequestFactory(ConnectorKey, FacadeKeyFunction, + new OperationRequest {SyncEventSubscriptionOpRequest = requestBuilder}, handler, + CancellationToken.None)); + } + + private class InternalRequestFactory : AbstractRemoteOperationRequestFactory + { + private readonly OperationRequest _operationRequest; + private readonly IObserver _handler; + + public InternalRequestFactory(Org.IdentityConnectors.Framework.Api.ConnectorKey connectorKey, + Func facadeKeyFunction, OperationRequest operationRequest, + IObserver handler, CancellationToken cancellationToken) + : base(connectorKey, facadeKeyFunction, cancellationToken) + { + _operationRequest = operationRequest; + _handler = handler; + } + + public override InternalRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + RPCRequest builder = CreateRpcRequest(context); + if (null != builder) + { + return new InternalRequest(context, requestId, completionCallback, builder, _handler, + CancellationToken); + } + else + { + return null; + } + } + + protected internal override OperationRequest CreateOperationRequest( + RemoteOperationContext remoteContext) + { + return _operationRequest; + } + } + + private class InternalRequest : + AbstractRemoteOperationRequestFactory.AbstractRemoteOperationRequest + + { + private readonly IObserver _handler; + private Int32 _confirmed = 0; + + public InternalRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, RPCRequest requestBuilder, + IObserver handler, CancellationToken cancellationToken) + : base(context, requestId, completionCallback, requestBuilder, cancellationToken) + { + _handler = handler; + } + + protected internal override SyncEventSubscriptionOpResponse GetOperationResponseMessages( + OperationResponse message) + { + if (null != message.SyncEventSubscriptionOpResponse) + { + return message.SyncEventSubscriptionOpResponse; + } + else + { + Trace.TraceInformation(OperationExpectsMessage, RequestId, "HasSyncEventSubscriptionOpResponse"); + return null; + } + } + + protected internal override void HandleOperationResponseMessages(WebSocketConnectionHolder sourceConnection, + SyncEventSubscriptionOpResponse message) + { + if (null != _handler && null != message.SyncDelta) + { + OBJ.SyncDelta delta = MessagesUtil.DeserializeMessage(message.SyncDelta); + try + { + _handler.OnNext(delta); + } + catch (Exception) + { + if (!Promise.IsCompleted) + { + HandleError(new ConnectorException("ResultsHandler stopped processing results")); + TryCancelRemote(ConnectionContext, RequestId); + } + } + } + else if (message.Completed && message.Completed) + { + HandleResult(null); + Trace.TraceInformation("Subscription is completed"); + } + else if (Interlocked.CompareExchange(ref _confirmed, 0, 1) == 0) + { + Trace.TraceInformation("Subscription has been made successfully on remote side"); + } + } + } + + // ---- + + public static AbstractLocalOperationProcessor + CreateProcessor(long requestId, WebSocketConnectionHolder socket, SyncEventSubscriptionOpRequest message) + { + return new InternalLocalOperationProcessor(requestId, socket, message); + } + + private class InternalLocalOperationProcessor : + AbstractLocalOperationProcessor, + IObserver + { + private OBJ.ISubscription _subscription; + + protected internal InternalLocalOperationProcessor(long requestId, WebSocketConnectionHolder socket, + SyncEventSubscriptionOpRequest message) + : base(requestId, socket, message) + { + } + + protected override RPCResponse CreateOperationResponse(RemoteOperationContext remoteContext, + SyncEventSubscriptionOpResponse result) + { + return new + RPCResponse + { + OperationResponse = new OperationResponse + { + SyncEventSubscriptionOpResponse = result + } + }; + } + + public override void Execute(ConnectorFacade connectorFacade) + { + try + { + TryHandleResult(ExecuteOperation(connectorFacade, _requestMessage)); + } + catch (Exception error) + { + HandleError(error); + } + } + + protected internal override SyncEventSubscriptionOpResponse ExecuteOperation( + ConnectorFacade connectorFacade, SyncEventSubscriptionOpRequest requestMessage) + { + OBJ.ObjectClass objectClass = new OBJ.ObjectClass(requestMessage.ObjectClass); + + OBJ.SyncToken token = null; + if (null != requestMessage.Token) + { + token = MessagesUtil.DeserializeMessage(requestMessage.Token); + } + + OBJ.OperationOptions operationOptions = null; + if (null != requestMessage.Options) + { + operationOptions = MessagesUtil.DeserializeLegacy(requestMessage.Options); + } + + _subscription = connectorFacade.Subscribe(objectClass, token, this, operationOptions); + + return new SyncEventSubscriptionOpResponse(); + } + + + protected override bool TryCancel() + { + _subscription.Dispose(); + return base.TryCancel(); + } + + public void OnNext(OBJ.SyncDelta value) + { + if (null != value) + { + if (!TryHandleResult(new + SyncEventSubscriptionOpResponse + { + SyncDelta = MessagesUtil.SerializeMessage(value) + })) + { + OnError(new Exception("Failed to Handle OnNext event.")); + } + } + } + + public void OnError(Exception error) + { + try + { + byte[] responseMessage = MessagesUtil.CreateErrorResponse(RequestId, error).ToByteArray(); + TrySendBytes(responseMessage, true); + } + catch (Exception t) + { + Trace.TraceInformation + ("Operation encountered an exception and failed to send the exception response {0}", t.Message); + } + } + + public void OnCompleted() + { + TryHandleResult(new SyncEventSubscriptionOpResponse {Completed = true}); + } + } + } + + #endregion + + internal class SubscriptionImpl : OBJ.ISubscription + { + private readonly CancellationTokenSource _cancellationTokenSource; + private readonly Task _childTask; + + public SubscriptionImpl(Task childTask, CancellationTokenSource cancellationTokenSource) + { + _childTask = childTask; + _cancellationTokenSource = cancellationTokenSource; + } + + public void Dispose() + { + if (!_cancellationTokenSource.IsCancellationRequested) + { + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); + } + } + + public bool Unsubscribed + { + get { return _childTask.IsCompleted; } + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/Framework.cs b/dotnet/framework/FrameworkServer/Framework.cs new file mode 100755 index 00000000..c4d1b12b --- /dev/null +++ b/dotnet/framework/FrameworkServer/Framework.cs @@ -0,0 +1,670 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Org.ForgeRock.OpenICF.Common.RPC; +using Org.ForgeRock.OpenICF.Framework.Remote; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Common; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Impl.Api; +using Org.IdentityConnectors.Framework.Impl.Api.Local; +using Org.IdentityConnectors.Framework.Impl.Api.Remote; +using RemoteConnectorInfoImpl = Org.ForgeRock.OpenICF.Framework.Remote.RemoteConnectorInfoImpl; + +namespace Org.ForgeRock.OpenICF.Framework +{ + + #region ConnectorFramework + + public class ConnectorFramework : IDisposable + { + public const string RemoteLibraryMissingException = "Remote Connection Library is not initialised"; + + private RemoteConnectionInfoManagerFactory _remoteConnectionInfoManagerFactory; + private ConnectionManagerConfig _connectionManagerConfig = new ConnectionManagerConfig(); + + private Int32 _isRunning = 1; + + public bool Running + { + get { return _isRunning == 1; } + } + + public virtual void Dispose() + { + if (Interlocked.CompareExchange(ref _isRunning, 0, 1) == 1) + { + // Notify CloseListeners + RemoteConnectionInfoManagerFactory factory = RemoteConnectionInfoManagerFactory; + if (null != factory) + { + //logger.ok("Closing RemoteConnectionInfoManagerFactory"); + factory.Dispose(); + } + + // We need to complete all pending Promises + /*while (remoteManagerCache.Any()) + { + foreach (AsyncRemoteLegacyConnectorInfoManager manager in remoteManagerCache.Values) + { + manager.Dispose(); + } + }*/ + + // We need to complete all pending Promises + foreach (AsyncLocalConnectorInfoManager manager in _localConnectorInfoManagerCache.Values) + { + manager.Dispose(); + } + _localConnectorInfoManagerCache.Clear(); + } + } + + //// + public ConnectorFacade NewInstance(APIConfiguration config) + { + ConnectorFacade ret; + APIConfigurationImpl impl = (APIConfigurationImpl) config; + AbstractConnectorInfo connectorInfo = impl.ConnectorInfo; + if (connectorInfo is LocalConnectorInfoImpl) + { + LocalConnectorInfoImpl localInfo = (LocalConnectorInfoImpl) connectorInfo; + try + { + // create a new Provisioner. + ret = new LocalConnectorFacadeImpl(localInfo, impl); + } + catch (Exception ex) + { + string connector = impl.ConnectorInfo.ConnectorKey.ToString(); + Debug.WriteLine("Failed to create new connector facade: {0}, {1}", connector, config); + TraceUtil.TraceException("Failed to create new connector facade", ex); + throw; + } + } + else if (connectorInfo is Org.IdentityConnectors.Framework.Impl.Api.Remote.RemoteConnectorInfoImpl) + { + ret = new Org.IdentityConnectors.Framework.Impl.Api.Remote.RemoteConnectorFacadeImpl(impl); + } + else if (connectorInfo is Org.ForgeRock.OpenICF.Framework.Remote.RemoteConnectorInfoImpl) + { + ret = new RemoteAsyncConnectorFacade(impl); + } + else + { + throw new System.ArgumentException("Unknown ConnectorInfo type"); + } + return ret; + } + + private readonly ConcurrentDictionary _managedFacadeCache = + new ConcurrentDictionary(); + + private Timer _scheduledManagedFacadeCacheTimer; + + public virtual ConnectorFacade NewManagedInstance(ConnectorInfo connectorInfo, string config) + { + return NewManagedInstance(connectorInfo, config, null); + } + + public virtual ConnectorFacade NewManagedInstance(ConnectorInfo connectorInfo, string config, + IConfigurationPropertyChangeListener changeListener) + { + ConnectorFacade facade; + _managedFacadeCache.TryGetValue(config, out facade); + if (null == facade) + { + // new ConnectorFacade creation must remain cheap operation + facade = NewInstance(connectorInfo, config, changeListener); + if (facade is LocalConnectorFacadeImpl) + { + ConnectorFacade ret = _managedFacadeCache.GetOrAdd(facade.ConnectorFacadeKey, facade); + if (null != ret) + { + Trace.TraceInformation("ConnectorFacade found in cache"); + facade = ret; + } + else + { + lock (_managedFacadeCache) + { + if (null == _scheduledManagedFacadeCacheTimer) + { + _scheduledManagedFacadeCacheTimer = new Timer(state => + { + foreach (var connectorFacade in _managedFacadeCache) + { + LocalConnectorFacadeImpl value = + connectorFacade.Value as LocalConnectorFacadeImpl; + if (null != value && value.IsUnusedFor(TimeSpan.FromHours(2))) + { + ConnectorFacade ignore; + _managedFacadeCache.TryRemove(connectorFacade.Key, out ignore); + if (ignore == value) + { + Trace.TraceInformation( + "LocalConnectorFacade is disposed after 120min inactivity"); + value.Dispose(); + } + } + } + }, _managedFacadeCache, TimeSpan.FromHours(2), TimeSpan.FromHours(2)); + } + } + } + } + } + return facade; + } + + public ConnectorFacade NewInstance(ConnectorInfo connectorInfo, string config) + { + return NewInstance(connectorInfo, config, null); + } + + public ConnectorFacade NewInstance(ConnectorInfo connectorInfo, string config, + IConfigurationPropertyChangeListener changeListener) + { + ConnectorFacade ret; + if (connectorInfo is LocalConnectorInfoImpl) + { + try + { + // create a new Provisioner. + ret = new LocalConnectorFacadeImpl((LocalConnectorInfoImpl) connectorInfo, config, changeListener); + } + catch (Exception) + { + Debug.WriteLine("Failed to create new connector facade: {0}, {1}", connectorInfo.ConnectorKey, + config); + throw; + } + } + else if (connectorInfo is Org.IdentityConnectors.Framework.Impl.Api.Remote.RemoteConnectorInfoImpl) + { + ret = + new RemoteConnectorFacadeImpl( + (Org.IdentityConnectors.Framework.Impl.Api.Remote.RemoteConnectorInfoImpl) connectorInfo, config, + changeListener); + } + else if (connectorInfo is Org.ForgeRock.OpenICF.Framework.Remote.RemoteConnectorInfoImpl) + { + Assertions.NullCheck(connectorInfo, "connectorInfo"); + APIConfigurationImpl configuration = + (APIConfigurationImpl) + SerializerUtil.DeserializeBase64Object(Assertions.NullChecked(config, "configuration")); + configuration.ConnectorInfo = (Remote.RemoteConnectorInfoImpl) connectorInfo; + + configuration.ChangeListener = changeListener; + ret = NewInstance(configuration); + } + else + { + throw new System.ArgumentException("Unknown ConnectorInfo type"); + } + return ret; + } + + // ------ LocalConnectorFramework Implementation Start ------ + + private readonly ConcurrentDictionary _localConnectorInfoManagerCache = + new ConcurrentDictionary(); + + public virtual AsyncLocalConnectorInfoManager LocalManager + { + get { return GetLocalConnectorInfoManager("default"); } + } + + public virtual AsyncLocalConnectorInfoManager GetLocalConnectorInfoManager( + String connectorBundleParentClassLoader) + { + String key = connectorBundleParentClassLoader ?? "default"; + AsyncLocalConnectorInfoManager manager; + _localConnectorInfoManagerCache.TryGetValue(key, out manager); + return manager ?? (_localConnectorInfoManagerCache.GetOrAdd(key, new AsyncLocalConnectorInfoManager())); + } + + // ------ LocalConnectorFramework Implementation End ------ + + // ------ Legacy RemoteConnectorInfoManager Support ------ + /* private readonly ConcurrentDictionary, AsyncRemoteLegacyConnectorInfoManager> remoteManagerCache = new ConcurrentDictionary, AsyncRemoteLegacyConnectorInfoManager>(); + + public virtual AsyncRemoteLegacyConnectorInfoManager getRemoteManager(RemoteFrameworkConnectionInfo info) + { + if (null == info) + { + return null; + } + if (Running) + { + Pair key = Pair.Of(info.ToLower(CultureInfo.GetCultureInfo("en"))), info.Port); + AsyncRemoteLegacyConnectorInfoManager rv = remoteManagerCache[key]; + if (rv == null) + { + lock (remoteManagerCache) + { + rv = remoteManagerCache[key]; + if (rv == null) + { + rv = new AsyncRemoteLegacyConnectorInfoManager(info, scheduler); + rv.addCloseListener((x)={remoteManagerCache.Remove(key);}); + if (!Running && remoteManagerCache.Remove(key) != null) + { + rv.close(); + throw new IllegalStateException("ConnectorFramework is shut down"); + } + } + remoteManagerCache[key] = rv; + } + } + return rv; + } + else + { + throw new InvalidOperationException("ConnectorFramework is shut down"); + } + }*/ + + // ------ RemoteConnectorFramework Implementation Start ------ + + public virtual AsyncRemoteConnectorInfoManager GetRemoteManager(RemoteWSFrameworkConnectionInfo info) + { + if (null == info) + { + return null; + } + return new AsyncRemoteConnectorInfoManager(RemoteConnectionInfoManagerFactory.Connect(info)); + } + + public virtual LoadBalancingConnectorInfoManager GetRemoteManager( + LoadBalancingAlgorithmFactory loadBalancingAlgorithmFactory) + { + if (null != loadBalancingAlgorithmFactory && + loadBalancingAlgorithmFactory.AsyncRemoteConnectorInfoManager.Any()) + { + return new LoadBalancingConnectorInfoManager(loadBalancingAlgorithmFactory); + } + return null; + } + + public const String RemoteLibraryMissingExceptionMsg = "Remote Connection Library is not initialised"; + + public virtual ConnectorFacade NewInstance(ConnectorInfo connectorInfo, + Func transformer) + { + if (null != _remoteConnectionInfoManagerFactory) + { + return null; + } + throw new System.NotSupportedException(RemoteLibraryMissingExceptionMsg); + } + + public virtual Task NewInstanceAsync(ConnectorKey key, + Func transformer) + { + if (null != _remoteConnectionInfoManagerFactory) + { + return null; + } + throw new System.NotSupportedException(RemoteLibraryMissingExceptionMsg); + } + + // ------ RemoteConnectorFramework Implementation End ------ + + public virtual RemoteConnectionInfoManagerFactory RemoteConnectionInfoManagerFactory + { + get + { + lock (this) + { + if (null == _remoteConnectionInfoManagerFactory && Running) + { + OpenICFServerAdapter listener = new OpenICFServerAdapter(this, ConnectionInfoManager, true); + try + { + _remoteConnectionInfoManagerFactory = new RemoteConnectionInfoManagerFactory(listener, + ConnectionManagerConfig); + } + catch (Exception e) + { + TraceUtil.TraceException("RemoteConnectionInfoManagerFactory is not available", e); + //remoteConnectionInfoManagerFactory = new RemoteConnectionInfoManagerFactoryAnonymousInnerClassHelper(this, listener, Client.ConnectionManagerConfig, e); + } + //_remoteConnectionInfoManagerFactory.AddCloseListener( new CloseListenerAnonymousInnerClassHelper(this)); + } + return _remoteConnectionInfoManagerFactory; + } + } + } + + protected internal virtual IAsyncConnectorInfoManager ConnectionInfoManager + { + get { return LocalManager; } + } + + public virtual ConnectionManagerConfig ConnectionManagerConfig + { + get { return _connectionManagerConfig; } + set { _connectionManagerConfig = value ?? new ConnectionManagerConfig(); } + } + } + + #endregion + + #region DelegatingAsyncConnectorInfoManager + + public abstract class DelegatingAsyncConnectorInfoManager : + DisposableAsyncConnectorInfoManager + { + protected readonly ConcurrentDictionary delegates = + new ConcurrentDictionary(); + + private readonly ConcurrentDictionary, Boolean> + _deferredRangePromiseCacheList; + + private readonly ConcurrentDictionary, Boolean> _deferredKeyPromiseCacheList; + + private readonly bool _allowDeferred; + + public delegate void CloseListener(DelegatingAsyncConnectorInfoManager connectorInfoManager); + + /// + /// Adds a event handler to listen to the Disposed event on the DelegatingAsyncConnectorInfoManager. + /// + public event EventHandler Disposed; + + protected DelegatingAsyncConnectorInfoManager(bool allowDeferred) + { + _allowDeferred = allowDeferred; + if (allowDeferred) + { + _deferredRangePromiseCacheList = + new ConcurrentDictionary, Boolean>(); + + _deferredKeyPromiseCacheList = new ConcurrentDictionary, Boolean>(); + Disposed += (sender, args) => + { + DelegatingAsyncConnectorInfoManager outerInstance = sender as DelegatingAsyncConnectorInfoManager; + if (null != outerInstance) + { + foreach ( + Pair promise in + outerInstance._deferredRangePromiseCacheList.Keys) + { + promise.Second.Shutdown(); + } + outerInstance._deferredRangePromiseCacheList.Clear(); + + foreach ( + Pair promise in + outerInstance._deferredKeyPromiseCacheList.Keys) + { + promise.Second.Shutdown(); + } + outerInstance._deferredKeyPromiseCacheList.Clear(); + } + }; + } + else + { + _deferredRangePromiseCacheList = null; + _deferredKeyPromiseCacheList = null; + } + } + + protected abstract + IRequestDistributor + MessageDistributor { get; } + + + protected virtual IReadOnlyCollection Delegates + { + get { return delegates.Keys as IReadOnlyCollection; } + } + + protected bool AddAsyncConnectorInfoManager(IAsyncConnectorInfoManager @delegate) + { + if (null != @delegate && delegates.TryAdd(@delegate, true)) + { + Trace.TraceInformation("Add AsyncConnectorInfoManager to delegates"); + OnAddAsyncConnectorInfoManager(@delegate); + return true; + } + return false; + } + + protected internal virtual bool RemoveAsyncConnectorInfoManager(IAsyncConnectorInfoManager @delegate) + { + bool ignore; + return null != @delegate && delegates.TryRemove(@delegate, out ignore); + } + + protected internal virtual void OnAddAsyncConnectorInfoManager(IAsyncConnectorInfoManager @delegate) + { + if (_allowDeferred) + { + foreach (Pair promise in _deferredRangePromiseCacheList.Keys) + { + promise.Second.Add(@delegate.FindConnectorInfoAsync(promise.First)); + } + foreach (Pair promise in _deferredKeyPromiseCacheList.Keys) + { + promise.Second.Add(@delegate.FindConnectorInfoAsync(promise.First)); + } + } + } + + private sealed class DeferredPromise : TaskCompletionSource + { + private Int32 _remaining; + + public DeferredPromise(bool neverFail) + { + _remaining = neverFail ? 1 : 0; + } + + public void Shutdown() + { + if (!Task.IsCompleted) + SetException(new InvalidOperationException("AsyncConnectorInfoManager is shut down!")); + } + + internal bool Add(Task promise) + { + Interlocked.Increment(ref _remaining); + promise.ContinueWith(task => + { + if (task.IsCompleted) + { + TrySetResult(task.Result); + } + else + { + if (Interlocked.Decrement(ref _remaining) == 0 && !Task.IsCompleted) + { + if (task.IsFaulted) + { + TrySetException(task.Exception ?? new Exception()); + } + else + { + TrySetCanceled(); + } + } + } + }); + return !Task.IsCompleted; + } + } + + public override async Task FindConnectorInfoAsync(ConnectorKey key) + { + if (!Running) + { + TaskCompletionSource promise = new TaskCompletionSource(); + promise.SetException(new InvalidOperationException("AsyncConnectorInfoManager is shut down!")); + return await promise.Task; + } + else + { + IEnumerator safeDelegates = Delegates.GetEnumerator(); + DeferredPromise promise = new DeferredPromise(_allowDeferred); + Pair entry = Pair.Of(key, promise); + + if (_allowDeferred) + { + _deferredKeyPromiseCacheList.TryAdd(entry, true); + } + + bool pending = true; + while (pending && safeDelegates.MoveNext()) + { + pending = promise.Add(safeDelegates.Current.FindConnectorInfoAsync(key)); + } + + if (_allowDeferred && Running) + { + if (pending) + { + promise.Task.ContinueWith(task => + { + bool ignore; + _deferredKeyPromiseCacheList.TryRemove(entry, out ignore); + }).ConfigureAwait(false); + } + else + { + bool ignore; + _deferredKeyPromiseCacheList.TryRemove(entry, out ignore); + } + } + else if (!Running) + { + promise.Shutdown(); + } + + return await + promise.Task.ContinueWith( + task => new RemoteConnectorInfoImpl(MessageDistributor, + (RemoteConnectorInfoImpl) task.Result)); + } + } + + public override async Task FindConnectorInfoAsync(ConnectorKeyRange keyRange) + { + if (!Running) + { + throw new InvalidOperationException("AsyncConnectorInfoManager is shut down!"); + } + if (keyRange.BundleVersionRange.Empty) + { + TaskCompletionSource result = new TaskCompletionSource(); + result.SetException(new ArgumentException("ConnectorBundle VersionRange is Empty")); + return await result.Task; + } + if (keyRange.BundleVersionRange.Exact) + { + return await FindConnectorInfoAsync(keyRange.ExactConnectorKey); + } + IEnumerator safeDelegates = Delegates.GetEnumerator(); + + DeferredPromise promise = new DeferredPromise(_allowDeferred); + Pair entry = + Pair.Of(keyRange, promise); + + if (_allowDeferred) + { + _deferredRangePromiseCacheList.TryAdd(entry, true); + } + + bool pending = true; + while (pending && safeDelegates.MoveNext()) + { + pending = promise.Add(safeDelegates.Current.FindConnectorInfoAsync(keyRange)); + } + if (_allowDeferred && Running) + { + if (pending) + { + promise.Task.ContinueWith((task, state) => + { + bool ignore; + _deferredRangePromiseCacheList.TryRemove( + (Pair) state, out ignore); + }, entry).ConfigureAwait(false); + } + else + { + bool ignore; + _deferredRangePromiseCacheList.TryRemove(entry, out ignore); + } + } + else if (!Running) + { + promise.Shutdown(); + } + + return await + promise.Task.ContinueWith( + task => new RemoteConnectorInfoImpl(MessageDistributor, + (RemoteConnectorInfoImpl) task.Result)); + } + + public override IList ConnectorInfos + { + get + { + List keys = new List(); + List result = new List(); + foreach (IAsyncConnectorInfoManager group in Delegates) + { + foreach (ConnectorInfo info in group.ConnectorInfos) + { + if (!keys.Contains(info.ConnectorKey)) + { + keys.Add(info.ConnectorKey); + result.Add(info); + } + } + } + return result; + } + } + + public override ConnectorInfo FindConnectorInfo(ConnectorKey key) + { + return Delegates.Select(@group => @group.FindConnectorInfo(key)).FirstOrDefault(result => null != result); + } + } + + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/FrameworkServer.csproj b/dotnet/framework/FrameworkServer/FrameworkServer.csproj new file mode 100755 index 00000000..e2d3b5bb --- /dev/null +++ b/dotnet/framework/FrameworkServer/FrameworkServer.csproj @@ -0,0 +1,109 @@ + + + + + + Debug + AnyCPU + {5B47BEFD-C60B-4E80-943E-A7151CEEA568} + Library + Properties + Org.ForgeRock.OpenICF.Framework.Remote + FrameworkServer + OpenICF Framework - Connector Server + v4.5.2 + 512 + False + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + ..\packages\BouncyCastle.Crypto.1.8.0-beta4\lib\net40\crypto.dll + + + ..\packages\Google.ProtocolBuffers.3\lib\Google.Protobuf.dll + + + + + + + + + + + + + + + + + + + {f140e8da-52b4-4159-992a-9da10ea8eefb} + Common + + + {5b011775-b121-4eee-a410-ba2d2f5bfb8b} + FrameworkInternal + + + {5a9e8c5b-4d41-4e3e-9680-6c195bfad47a} + FrameworkProtoBuf + + + {b85c5a35-e3a2-4b04-9693-795e57d66de2} + FrameworkRpc + + + {8b24461b-456a-4032-89a1-cd418f7b5b62} + Framework + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/Local.cs b/dotnet/framework/FrameworkServer/Local.cs new file mode 100755 index 00000000..499d018f --- /dev/null +++ b/dotnet/framework/FrameworkServer/Local.cs @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Org.IdentityConnectors.Framework.Impl.Api.Local; + +namespace Org.ForgeRock.OpenICF.Framework.Remote +{ + public class AsyncLocalConnectorInfoManager : + ManagedAsyncConnectorInfoManager + { + public virtual void AddConnectorAssembly(ICollection connectorAssemblyFileInfos) + { + foreach (FileInfo assemblyFile in connectorAssemblyFileInfos) + { + Assembly lib = Assembly.LoadFrom(assemblyFile.ToString()); + AddConnectorAssembly(lib); + } + } + + public virtual void AddConnectorAssembly(Assembly connectorAssembly) + { + foreach (var connectorInfo in ConnectorAssemblyUtility.ProcessAssembly(connectorAssembly)) + { + AddConnectorInfo(connectorInfo as LocalConnectorInfoImpl); + } + } + + protected internal override bool CanCloseNow() + { + DoClose(); + return false; + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/Remote.cs b/dotnet/framework/FrameworkServer/Remote.cs new file mode 100755 index 00000000..a67859c8 --- /dev/null +++ b/dotnet/framework/FrameworkServer/Remote.cs @@ -0,0 +1,2408 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Google.Protobuf; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.EC; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Tls; +using Org.BouncyCastle.Security; +using Org.ForgeRock.OpenICF.Common.RPC; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Proxy; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Impl.Api; +using Org.IdentityConnectors.Framework.Impl.Api.Remote; +using Org.IdentityConnectors.Framework.Spi; +using OBJ = Org.IdentityConnectors.Framework.Common.Objects; +using API = Org.IdentityConnectors.Framework.Api; +using PRB = Org.ForgeRock.OpenICF.Common.ProtoBuf; + +namespace Org.ForgeRock.OpenICF.Framework.Remote +{ + + #region AsyncRemoteConnectorInfoManager + + /// + /// An AsyncRemoteConnectorInfoManager. + /// + /// Since 1.5 + public class AsyncRemoteConnectorInfoManager : DelegatingAsyncConnectorInfoManager + { + protected readonly IDisposable RemoteDisposable; + + private readonly + IRequestDistributor + _messageDistributor; + + public AsyncRemoteConnectorInfoManager(IDisposable remoteDisposable, + IRequestDistributor + messageDistributor) + : base(false) + { + RemoteDisposable = remoteDisposable; + _messageDistributor = messageDistributor; + } + + public AsyncRemoteConnectorInfoManager(IRemoteConnectorInfoManager loadBalancingAlgorithm) + : this(loadBalancingAlgorithm, loadBalancingAlgorithm.RequestDistributor) + { + AddAsyncConnectorInfoManager(loadBalancingAlgorithm.AsyncConnectorInfoManager); + } + + protected AsyncRemoteConnectorInfoManager(LoadBalancingAlgorithmFactory loadBalancingAlgorithmFactory) + : this( + null, + loadBalancingAlgorithmFactory.NewInstance( + loadBalancingAlgorithmFactory.AsyncRemoteConnectorInfoManager.Select( + manager => manager.MessageDistributor))) + { + if ( + loadBalancingAlgorithmFactory.AsyncRemoteConnectorInfoManager.SelectMany( + @delegate => @delegate.Delegates).Any(am => !AddAsyncConnectorInfoManager(am))) + { + throw new ArgumentException("Possible circular or repeated remote in LoadBalancing tree"); + } + } + + + protected override + IRequestDistributor + MessageDistributor + { + get { return _messageDistributor; } + } + + protected override void DoClose() + { + if (null != RemoteDisposable) + { + try + { + RemoteDisposable.Dispose(); + } + catch (Exception e) + { + TraceUtil.TraceException("Failed to close underlying remote connection", e); + } + } + } + } + + #endregion + + /* + #region AsyncRemoteLegacyConnectorInfoManager + +/// +/// @since 1.5 +/// +public class AsyncRemoteLegacyConnectorInfoManager : ManagedAsyncConnectorInfoManager +{ + + protected internal readonly ConnectorEventHandler handler = new ConnectorEventHandlerAnonymousInnerClassHelper(); + + private class ConnectorEventHandlerAnonymousInnerClassHelper : ConnectorEventHandler + { + public ConnectorEventHandlerAnonymousInnerClassHelper() + { + } + public virtual void handleEvent(ConnectorEvent @event) + { + if (ConnectorEvent.CONNECTOR_REGISTERED.Equals(@event.Topic)) + { + ConnectorInfo connectorInfo = outerInstance.@delegate.findConnectorInfo((ConnectorKey) @event.Source); + addConnectorInfo((RemoteConnectorInfoImpl) connectorInfo); + } + } + } + private readonly RemoteConnectorInfoManagerImpl @delegate; + + private readonly ScheduledFuture future; + +//JAVA TO C# CONVERTER WARNING: 'final' parameters are not allowed in .NET: +//ORIGINAL LINE: public AsyncRemoteLegacyConnectorInfoManager(final RemoteFrameworkConnectionInfo info, final ScheduledExecutorService scheduler) + public AsyncRemoteLegacyConnectorInfoManager(RemoteFrameworkConnectionInfo info, ScheduledExecutorService scheduler) + { + this.@delegate = new RemoteConnectorInfoManagerImpl(info, false); + @delegate.addConnectorEventHandler(handler); + long heartbeatInterval = info.HeartbeatInterval; + if (heartbeatInterval <= 0) + { + heartbeatInterval = 60L; + } + try + { + future = scheduler.scheduleAtFixedRate(@delegate, 0, heartbeatInterval, TimeUnit.SECONDS); + logger.info("Legacy ConnectorServer Heartbeat scheduled to {0} by {1} seconds", info, heartbeatInterval); + } + catch (RejectedExecutionException e) + { + throw new ConnectorException(e.Message, e); + } + } + + protected internal virtual void doClose() + { + future.cancel(true); + @delegate.deleteConnectorEventHandler(handler); + base.doClose(); + } + + public override IList ConnectorInfos + { + get + { + return @delegate.ConnectorInfos; + } + } + + public override ConnectorInfo FindConnectorInfo(ConnectorKey key) + { + return @delegate.FindConnectorInfo(key); + } + +} + #endregion + */ + + #region ConnectionPrincipal + + public abstract class ConnectionPrincipal : GenericPrincipal, IDisposable + , IRequestDistributor + { + public const string DefaultName = "anonymous"; + + protected internal Int32 isRunning = 1; + + protected readonly ConcurrentDictionary ConnectionGroups = + new ConcurrentDictionary(); + + private readonly ConcurrentDictionary _globalConnectionGroups; + + private readonly IMessageListener + _listener; + + public event EventHandler Disposed; + + protected ConnectionPrincipal( + IMessageListener listener, + ConcurrentDictionary globalConnectionGroups) + : base(new GenericIdentity(DefaultName), new[] { "connector" }) + { + _listener = listener; + _globalConnectionGroups = globalConnectionGroups; + } + + + public virtual RemoteOperationContext Handshake(WebSocketConnectionHolder webSocketConnection, + PRB.HandshakeMessage message) + { + WebSocketConnectionGroup newConnectionGroup = new WebSocketConnectionGroup(message.SessionId); + WebSocketConnectionGroup connectionGroup = _globalConnectionGroups.GetOrAdd(message.SessionId, + newConnectionGroup); + if (newConnectionGroup == connectionGroup) + { + ConnectionGroups.TryAdd(message.SessionId, connectionGroup); + try + { + OnNewWebSocketConnectionGroup(connectionGroup); + } + catch (Exception ignore) + { +#if DEBUG + System.Text.StringBuilder sb = new System.Text.StringBuilder("Failed to notify onNewWebSocketConnectionGroup - "); + TraceUtil.ExceptionToString(sb, ignore, String.Empty); + Debug.WriteLine(sb.ToString()); +#endif + } + } + return connectionGroup.Handshake(this, webSocketConnection, message); + } + + protected virtual void OnNewWebSocketConnectionGroup(WebSocketConnectionGroup connectionGroup) + { + } + + public virtual IMessageListener + OperationMessageListener + { + get { return _listener; } + } + + public virtual TR TrySubmitRequest( + IRemoteRequestFactory + + requestFactory) + where TR : + RemoteRequest + where TE : Exception + { + return + (from e in ConnectionGroups.Values where e.Operational select e.TrySubmitRequest(requestFactory)) + .FirstOrDefault(result => null != result); + } + + public virtual bool Operational + { + get { return isRunning == 1 && ConnectionGroups.Values.Any(e => e.Operational); } + } + + /// + /// Closes this manager and releases any system resources associated with it. + /// If the manager is already closed then invoking this method has no effect. + /// + protected abstract void DoClose(); + + public void Dispose() + { + if (Interlocked.CompareExchange(ref isRunning, 0, 1) == 1) + { + DoClose(); + // Notify CloseListeners + OnDisposed(); + } + } + + protected virtual void OnDisposed() + { + var handler = Disposed; + if (handler != null) handler(this, EventArgs.Empty); + } + } + + #endregion + + #region FailoverLoadBalancingAlgorithmFactory + + public class FailoverLoadBalancingAlgorithmFactory : LoadBalancingAlgorithmFactory + { + protected override + IRequestDistributor + CreateLoadBalancer( + IList> + delegates) + { + return + new FailoverLoadBalancingAlgorithm + (delegates); + } + } + + #endregion + + #region LoadBalancingAlgorithmFactory + + public abstract class LoadBalancingAlgorithmFactory + { + private readonly IList _remoteConnectorInfoManagers = + new List(); + + public virtual void AddAsyncRemoteConnectorInfoManager( + AsyncRemoteConnectorInfoManager remoteConnectorInfoManager) + { + if (null != remoteConnectorInfoManager) _remoteConnectorInfoManagers.Add(remoteConnectorInfoManager); + } + + public virtual ICollection AsyncRemoteConnectorInfoManager + { + get { return _remoteConnectorInfoManagers; } + } + + public virtual IRequestDistributor + NewInstance( + IEnumerable + > + parameter) + { + IList> + delegates = parameter.Where(dist => null != dist).ToList(); + if (delegates.Count == 0) + { + throw new ArgumentException("The LoadBalancing delegates is empty"); + } + return CreateLoadBalancer(delegates); + } + + protected abstract + IRequestDistributor + CreateLoadBalancer( + IList> + delegates); + } + + #endregion + + #region LoadBalancingConnectorFacadeContext + + /// + /// A LoadBalancingConnectorFacadeContext gives contextual information about the + /// current executing + /// + /// + public interface ILoadBalancingConnectorFacadeContext + { + /// + /// Loads the and + /// class in order + /// to determine the proper default configuration parameters. + /// + /// default APIConfiguration + API.APIConfiguration ApiConfiguration { get; } + + /// + /// Gets the principal name of executing + /// . + /// + /// + /// name of + /// + /// + string PrincipalName { get; } + + /// + /// Get the RemoteOperationContext of executing + /// + /// + /// + /// context of + /// + /// + RemoteOperationContext RemoteOperationContext { get; } + } + + #endregion + + #region LoadBalancingConnectorInfoManager + + /// Since 1.5 + public class LoadBalancingConnectorInfoManager : AsyncRemoteConnectorInfoManager + { + public LoadBalancingConnectorInfoManager(LoadBalancingAlgorithmFactory loadBalancingAlgorithmFactory) + : base(loadBalancingAlgorithmFactory) + { + } + + public virtual Task NewInstance(API.ConnectorKey key, + Func transformer) + { + return FindConnectorInfoAsync(key).ContinueWith((task, state) => + { + if (task.Result is RemoteConnectorInfoImpl) + { + //return new RemoteAsyncConnectorFacade((RemoteConnectorInfoImpl) task.Result, transformer); + return default(API.ConnectorFacade); + } + else + { + throw new ArgumentException("Invalid RemoteConnectorInfoImpl"); + } + }, transformer); + } + } + + #endregion + + #region ManagedAsyncConnectorInfoManager + + public class ManagedAsyncConnectorInfoManager : DisposableAsyncConnectorInfoManager + where TV : API.ConnectorInfo + where TC : ManagedAsyncConnectorInfoManager + { + private readonly ConcurrentDictionary> _managedConnectorInfos = + new ConcurrentDictionary>(); + + private class ConnectorKeyComparator : IComparer + { + /// + /// Descending ConnectorKey Comparator. + /// + /// + /// the first object to be compared. + /// + /// + /// the second object to be compared. + /// + /// + /// a negative integer, zero, or a positive + /// integer as the first argument is less than, + /// equal to, or greater than the second. + /// + public virtual int Compare(API.ConnectorKey left, API.ConnectorKey right) + { + int result = String.Compare(left.BundleName, right.BundleName, StringComparison.Ordinal); + if (result != 0) + { + return result * -1; + } + result = String.Compare(left.ConnectorName, right.ConnectorName, StringComparison.Ordinal); + if (result != 0) + { + return result * -1; + } + return String.Compare(left.BundleVersion, right.BundleVersion, StringComparison.Ordinal) * -1; + } + } + + private readonly ConcurrentDictionary>, Boolean> + _rangePromiseCacheList = + new ConcurrentDictionary>, Boolean>(); + + public const String ClosedExceptionMsg = "AsyncConnectorInfoManager is shut down!"; + + + protected override void DoClose() + { + foreach (ConnectorEntry entry in _managedConnectorInfos.Values) + { + entry.Shutdown(); + } + _managedConnectorInfos.Clear(); + foreach ( + Pair> entry in _rangePromiseCacheList.Keys) + { + entry.Second.SetException( + new InvalidOperationException("ManagedAsyncConnectorInfoManager is shutting down!")); + } + _rangePromiseCacheList.Clear(); + } + + private Int32 _revision; + + public virtual bool IsChanged(int lastRevision) + { + return lastRevision != _revision; + } + + protected internal virtual void AddConnectorInfo(TV connectorInfo) + { + ConnectorEntry entry = _managedConnectorInfos.GetOrAdd(connectorInfo.ConnectorKey, + new ConnectorEntry()); + + Trace.TraceInformation("Add new ConnectorInfo: {0}", connectorInfo.ConnectorKey); + + entry.ConnectorInfo = connectorInfo; + Interlocked.Increment(ref _revision); + + foreach ( + var rangeEntry in _rangePromiseCacheList.Keys.Where(x => x.First.IsInRange(connectorInfo.ConnectorKey))) + { + rangeEntry.Second.SetResult(connectorInfo); + } + } + + public override IList ConnectorInfos + { + get + { + List resultList = new List(_managedConnectorInfos.Count); + resultList.AddRange( + (from entry in _managedConnectorInfos.Values + where null != entry.ConnectorInfo + select entry.ConnectorInfo).Select(dummy => (API.ConnectorInfo)dummy)); + return resultList; + } + } + + public override API.ConnectorInfo FindConnectorInfo(API.ConnectorKey key) + { + ConnectorEntry entry; + _managedConnectorInfos.TryGetValue(key, out entry); + if (null != entry) + { + return entry.ConnectorInfo; + } + return null; + } + + public override Task FindConnectorInfoAsync(ConnectorKeyRange keyRange) + { + if (IsRunning == 0) + { + var result = new TaskCompletionSource(); + result.SetException(new InvalidOperationException("AsyncConnectorInfoManager is shut down!")); + return result.Task; + } + else + { + if (keyRange.BundleVersionRange.Empty) + { + var result = new TaskCompletionSource(); + result.SetException(new ArgumentException("ConnectorBundle VersionRange is Empty")); + return result.Task; + } + else if (keyRange.BundleVersionRange.Exact) + { + return FindConnectorInfoAsync(keyRange.ExactConnectorKey); + } + else + { + Pair> cacheEntry = + Pair>.Of(keyRange, + new TaskCompletionSource()); + + cacheEntry.Second.Task.ContinueWith((_, e) => + { + bool ignore; + Pair> key = + e as Pair>; + if (null != key) + _rangePromiseCacheList.TryRemove(key, out ignore); + }, cacheEntry); + _rangePromiseCacheList.TryAdd(cacheEntry, true); + + foreach (KeyValuePair> entry in _managedConnectorInfos) + { + API.ConnectorInfo connectorInfo = entry.Value.ConnectorInfo; + if (null != connectorInfo && keyRange.IsInRange(connectorInfo.ConnectorKey)) + { + cacheEntry.Second.SetResult(connectorInfo); + return cacheEntry.Second.Task; + } + } + + if (IsRunning != 1) + { + bool ignore; + _rangePromiseCacheList.TryRemove(cacheEntry, out ignore); + var result = new TaskCompletionSource(); + result.SetException(new InvalidOperationException(ClosedExceptionMsg)); + return result.Task; + } + return cacheEntry.Second.Task; + } + } + } + + public override async Task FindConnectorInfoAsync(API.ConnectorKey key) + { + if (IsRunning != 1) + { + throw new InvalidOperationException("AsyncConnectorInfoManager is shut down!"); + } + + TaskCompletionSource promise = new TaskCompletionSource(); + ConnectorEntry entry = _managedConnectorInfos.GetOrAdd(key, new ConnectorEntry()); + entry.AddOrFirePromise(promise); + if (IsRunning != 1 && !promise.Task.IsCompleted) + { + promise.SetException(new InvalidOperationException(ClosedExceptionMsg)); + } + return await promise.Task; + } + } + + + internal class ConnectorEntry where TV : API.ConnectorInfo + { + private TV _connectorInfo; + + private readonly ConcurrentQueue> _listeners = + new ConcurrentQueue>(); + + internal virtual TV ConnectorInfo + { + set + { + _connectorInfo = value; + TaskCompletionSource listener; + while (_listeners.TryDequeue(out listener)) + { + Trace.TraceInformation("Complete TaskSource:{0}", listener.Task.Id); + listener.SetResult(_connectorInfo); + } + } + get { return _connectorInfo; } + } + + internal virtual void Shutdown() + { + TaskCompletionSource listener; + while (_listeners.TryDequeue(out listener)) + { + //listener.SetException(CLOSED_EXCEPTION); + } + } + + internal virtual void AddOrFirePromise(TaskCompletionSource listener) + { + API.ConnectorInfo registered = _connectorInfo; + if (null != registered && !listener.Task.IsCompleted) + { + listener.SetResult(registered); + } + else + { + _listeners.Enqueue(listener); + API.ConnectorInfo registeredAfter = _connectorInfo; + if (null != registeredAfter && !listener.Task.IsCompleted) + { + listener.SetResult(registeredAfter); + } + } + } + } + + #endregion + + #region MessagesUtil + + public class MessagesUtil + { + public static PRB.RemoteMessage CreateErrorResponse(long messageId, Exception error) + { + PRB.RPCResponse builder = new PRB.RPCResponse + { + Error = FromException(error, 4) + }; + var message = CreateRemoteMessage(messageId); + message.Response = builder; + return message; + } + + public static PRB.ExceptionMessage FromException(Exception error, int depth) + { + PRB.ExceptionMessage builder = new PRB.ExceptionMessage + { + ExceptionClass = error.GetType().FullName, + StackTrace = error.StackTrace, + Message = error.Message + }; + + PRB.ExceptionMessage.Types.InnerCause cause = FromCause(error.InnerException, depth); + if (null != cause) + { + builder.InnerCause = cause; + } + + return builder; + } + + private static PRB.ExceptionMessage.Types.InnerCause FromCause(Exception error, int depth) + { + if (null != error && depth > 0) + { + PRB.ExceptionMessage.Types.InnerCause builder = new + PRB.ExceptionMessage.Types.InnerCause + { + ExceptionClass = error.GetType().FullName, + Message = error.Message + }; + + PRB.ExceptionMessage.Types.InnerCause cause = FromCause(error.InnerException, --depth); + if (null != cause) + { + builder.Cause = cause; + } + return builder; + } + return null; + } + + public static Exception FromExceptionMessage(PRB.ExceptionMessage exceptionMessage) + { + string message = null; + try + { + string throwableClass = exceptionMessage.ExceptionClass ?? typeof(ConnectorException).FullName; + message = exceptionMessage.Message ?? ""; + string stackTrace = !String.IsNullOrEmpty(exceptionMessage.StackTrace) + ? exceptionMessage.StackTrace + : null; + + return new RemoteWrappedException(throwableClass, message, GetCause(exceptionMessage.InnerCause), + stackTrace); + } + catch (Exception t) + { + return + new ConnectorException( + !String.IsNullOrEmpty(message) ? message : "Failed to process ExceptionMessage response", t); + } + } + + private static RemoteWrappedException GetCause(PRB.ExceptionMessage.Types.InnerCause cause) + { + if (null != cause) + { + string throwableClass = cause.ExceptionClass ?? typeof(ConnectorException).FullName; + string message = cause.Message ?? ""; + RemoteWrappedException originalCause = cause.Cause != null ? GetCause(cause.Cause) : null; + return new RemoteWrappedException(throwableClass, message, originalCause, null); + } + return null; + } + + public static PRB.Uid FromUid(PRB.Uid uid) + { + PRB.Uid builder = new PRB.Uid + { + Value = uid.Value + }; + if (null != uid.Revision) + { + builder.Revision = uid.Value; + } + return builder; + } + + public static PRB.RemoteMessage CreateResponse(long messageId, PRB.RPCResponse builderForValue) + { + var message = CreateRemoteMessage(messageId); + message.Response = builderForValue; + return message; + } + + public static PRB.RemoteMessage CreateRequest(int messageId, PRB.RPCRequest builderForValue) + { + var message = CreateRemoteMessage(messageId); + message.Request = builderForValue; + return message; + } + + public static PRB.RemoteMessage CreateRemoteMessage(long messageId) + { + PRB.RemoteMessage builder = new PRB.RemoteMessage + { + MessageId = 0 + }; + if (0 != messageId) + { + builder.MessageId = messageId; + } + return builder; + } + + [Obsolete] + public static T DeserializeLegacy(ByteString byteString) + { + if (null == byteString || byteString.IsEmpty) + { + return default(T); + } + return (T)SerializerUtil.DeserializeBinaryObject(byteString.ToByteArray()); + } + + [Obsolete] + public static ByteString SerializeLegacy(object source) + { + if (null != source) + { + return ByteString.CopyFrom(SerializerUtil.SerializeBinaryObject(source)); + } + return ByteString.Empty; + } + + public static T DeserializeMessage(Object source) + { + if (source == null) + { + return default(T); + } + var type = typeof(T); + // UID + if (typeof(IdentityConnectors.Framework.Common.Objects.Uid) == type) + { + var from = source as PRB.Uid; + if (null != from) + { + if (String.IsNullOrEmpty(from.Revision)) + { + return (T)(object)new IdentityConnectors.Framework.Common.Objects.Uid(from.Value); + } + return (T)(object)new IdentityConnectors.Framework.Common.Objects.Uid(from.Value, from.Revision); + } + } + //ConnectorKey + if (typeof(API.ConnectorKey) == type) + { + var from = source as Common.ProtoBuf.ConnectorKey; + if (null != from) + { + return (T)(object)new API.ConnectorKey(from.BundleName, from.BundleVersion, from.ConnectorName); + } + } + //ScriptContext + if (typeof(OBJ.ScriptContext) == type) + { + var from = source as Common.ProtoBuf.ScriptContext; + if (null != from) + { + var options = DeserializeLegacy>(from.ScriptArguments); + IDictionary arguments = + CollectionUtil.NewDictionary(options); + return + (T) + (object) + new OBJ.ScriptContext(from.Script.ScriptLanguage, from.Script.ScriptText, + arguments ?? new Dictionary()); + } + } + //SearchResult + if (typeof(OBJ.SearchResult) == type) + { + var from = source as Common.ProtoBuf.SearchResult; + if (null != from) + { + OBJ.SearchResult.CountPolicy policy = OBJ.SearchResult.CountPolicy.NONE; + + switch (from.TotalPagedResultsPolicy) + { + case PRB.SearchResult.Types.CountPolicy.EXACT: + { + policy = OBJ.SearchResult.CountPolicy.EXACT; + break; + } + case PRB.SearchResult.Types.CountPolicy.ESTIMATE: + { + policy = OBJ.SearchResult.CountPolicy.ESTIMATE; + break; + } + default: policy = OBJ.SearchResult.CountPolicy.NONE; + break; + } + return (T)(object)new OBJ.SearchResult(from.PagedResultsCookie, policy, from.TotalPagedResults, from.RemainingPagedResults); + } + } + //ConnectorObject + if (typeof(OBJ.ConnectorObject) == type) + { + var from = source as Common.ProtoBuf.ConnectorObject; + if (null != from) + { + ICollection attsObj = DeserializeLegacy>(from.Attributes); + ICollection atts = + CollectionUtil.NewSet(attsObj); + return (T)(object)new OBJ.ConnectorObject(new OBJ.ObjectClass(from.ObjectClass), atts); + } + } + //Locale/CultureInfo + if (typeof(CultureInfo) == type) + { + var from = source as Common.ProtoBuf.Locale; + if (null != from) + { + return + (T) + (object) + new Org.IdentityConnectors.Common.Locale(from.Language, from.Country, from.Variant) + .ToCultureInfo(); + } + } + //SyncToken + if (typeof(OBJ.SyncToken) == type) + { + var from = source as Common.ProtoBuf.SyncToken; + if (null != from) + { + return (T)(object)new OBJ.SyncToken(DeserializeLegacy(from.Value)); + } + } + //SyncDelta + if (typeof(IdentityConnectors.Framework.Common.Objects.SyncDelta) == type) + { + var from = source as Org.ForgeRock.OpenICF.Common.ProtoBuf.SyncDelta; + if (null != from) + { + OBJ.SyncDeltaBuilder builder = new OBJ.SyncDeltaBuilder(); + builder.Token = DeserializeMessage(from.Token); + switch (from.DeltaType) + { + case Common.ProtoBuf.SyncDelta.Types.SyncDeltaType.CREATE: + builder.DeltaType = OBJ.SyncDeltaType.CREATE; + break; + case Common.ProtoBuf.SyncDelta.Types.SyncDeltaType.CREATE_OR_UPDATE: + builder.DeltaType = OBJ.SyncDeltaType.CREATE_OR_UPDATE; + break; + case Common.ProtoBuf.SyncDelta.Types.SyncDeltaType.UPDATE: + builder.DeltaType = OBJ.SyncDeltaType.UPDATE; + break; + case Common.ProtoBuf.SyncDelta.Types.SyncDeltaType.DELETE: + builder.DeltaType = OBJ.SyncDeltaType.DELETE; + break; + } + if (from.PreviousUid != null) + { + builder.PreviousUid = + DeserializeMessage(from.PreviousUid); + } + if (!String.IsNullOrEmpty(from.ObjectClass)) + { + builder.ObjectClass = new OBJ.ObjectClass(from.ObjectClass); + } + if (from.Uid != null) + { + builder.Uid = DeserializeMessage(from.Uid); + } + if (!from.ConnectorObject.IsEmpty) + { + ICollection attsObj = DeserializeLegacy>(from.ConnectorObject); + ICollection atts = + CollectionUtil.NewSet(attsObj); + builder.Object = new OBJ.ConnectorObject(builder.ObjectClass, atts); + } + return (T)(object)builder.Build(); + } + } + + throw new NotImplementedException("From not supported"); + } + + public static T SerializeMessage(Object source) + { + if (source == null) + { + return default(T); + } + var type = typeof(T); + // UID + if (typeof(PRB.Uid) == type) + { + var from = source as IdentityConnectors.Framework.Common.Objects.Uid; + if (null != from) + { + var to = new PRB.Uid + { + Value = from.GetUidValue() + }; + if (null != from.Revision) to.Revision = from.Revision; + return (T)(object)to; + } + } + //ConnectorKey + if (typeof(Common.ProtoBuf.ConnectorKey) == type) + { + var from = source as API.ConnectorKey; + if (null != from) + { + return (T)(object)new Common.ProtoBuf.ConnectorKey + { + BundleName = from.BundleName, + BundleVersion = from.BundleVersion, + ConnectorName = from.ConnectorName + }; + } + } + //ScriptContext + if (typeof(Common.ProtoBuf.ScriptContext) == type) + { + var from = source as OBJ.ScriptContext; + if (null != from) + { + return (T)(object)new Common.ProtoBuf.ScriptContext + { + Script = new Common.ProtoBuf.Script + { + ScriptLanguage = from.ScriptLanguage, + ScriptText = from.ScriptText + }, + ScriptArguments = SerializeLegacy(from.ScriptArguments) + }; + } + } + //SearchResult + if (typeof(Common.ProtoBuf.SearchResult) == type) + { + var from = source as OBJ.SearchResult; + if (null != from) + { + PRB.SearchResult.Types.CountPolicy policy; + switch (from.TotalPagedResultsPolicy) + { + case OBJ.SearchResult.CountPolicy.EXACT: + { + policy = PRB.SearchResult.Types.CountPolicy.EXACT; + break; + } + case OBJ.SearchResult.CountPolicy.ESTIMATE: + { + policy = PRB.SearchResult.Types.CountPolicy.ESTIMATE; + break; + } + default: + policy = PRB.SearchResult.Types.CountPolicy.NONE; + break; + } + + return + (T) + (object) + new Common.ProtoBuf.SearchResult + { + PagedResultsCookie = from.PagedResultsCookie, + TotalPagedResultsPolicy = policy, + TotalPagedResults = from.TotalPagedResults, + RemainingPagedResults = from.RemainingPagedResults + }; + } + } + //ConnectorObject + if (typeof(Common.ProtoBuf.ConnectorObject) == type) + { + var from = source as OBJ.ConnectorObject; + if (null != from) + { + return (T)(object)new Common.ProtoBuf.ConnectorObject + { + ObjectClass = from.ObjectClass.GetObjectClassValue(), + Attributes = SerializeLegacy(from.GetAttributes()) + }; + } + } + //Locale/CultureInfo + if (typeof(Common.ProtoBuf.Locale) == type) + { + var from = source as CultureInfo; + if (null != from) + { + Org.IdentityConnectors.Common.Locale locale = Org.IdentityConnectors.Common.Locale.FindLocale(from); + return (T)(object)new PRB.Locale + { + Language = locale.Language, + Country = locale.Country, + Variant = locale.Variant + }; + } + } + //SyncToken + if (typeof(Common.ProtoBuf.SyncToken) == type) + { + var from = source as OBJ.SyncToken; + if (null != from) + { + return (T)(object)new Common.ProtoBuf.SyncToken + { + Value = SerializeLegacy(from.Value) + }; + } + } + //SyncDelta + if (typeof(Org.ForgeRock.OpenICF.Common.ProtoBuf.SyncDelta) == type) + { + var from = source as IdentityConnectors.Framework.Common.Objects.SyncDelta; + if (null != from) + { + Org.ForgeRock.OpenICF.Common.ProtoBuf.SyncDelta builder = + new Org.ForgeRock.OpenICF.Common.ProtoBuf.SyncDelta(); + builder.Token = SerializeMessage(from.Token); + + + switch (from.DeltaType) + { + case OBJ.SyncDeltaType.CREATE: + builder.DeltaType = + Org.ForgeRock.OpenICF.Common.ProtoBuf.SyncDelta.Types.SyncDeltaType.CREATE; + break; + case OBJ.SyncDeltaType.CREATE_OR_UPDATE: + builder.DeltaType = + Org.ForgeRock.OpenICF.Common.ProtoBuf.SyncDelta.Types.SyncDeltaType.CREATE_OR_UPDATE; + break; + case OBJ.SyncDeltaType.UPDATE: + builder.DeltaType = + Org.ForgeRock.OpenICF.Common.ProtoBuf.SyncDelta.Types.SyncDeltaType.UPDATE; + break; + case OBJ.SyncDeltaType.DELETE: + builder.DeltaType = + Org.ForgeRock.OpenICF.Common.ProtoBuf.SyncDelta.Types.SyncDeltaType.DELETE; + break; + } + if (null != from.Uid) + { + builder.Uid = SerializeMessage(from.Uid); + } + if (null != from.ObjectClass) + { + builder.ObjectClass = from.ObjectClass.GetObjectClassValue(); + } + if (null != from.Object) + { + builder.ConnectorObject = SerializeLegacy(from.Object.GetAttributes()); + } + if (null != from.PreviousUid) + { + builder.PreviousUid = SerializeMessage(from.PreviousUid); + } + + return (T)(object)builder; + } + } + throw new NotImplementedException("To not supported"); + } + } + + #endregion + + #region OpenICFServerAdapter + + public class OpenICFServerAdapter : + IMessageListener + { + public static TraceSource traceSource = new TraceSource("OpenICFServerAdapter"); + //private readonly KeyPair keyPair = SecurityUtil.generateKeyPair(); + + private readonly ConnectorFramework _connectorFramework; + private readonly PRB.HandshakeMessage _handshakeMessage; + private readonly IAsyncConnectorInfoManager _connectorInfoManager; + + public OpenICFServerAdapter(ConnectorFramework framework, IAsyncConnectorInfoManager defaultConnectorInfoManager, + bool isClient) + { + Client = isClient; + _connectorFramework = Assertions.NullChecked(framework, "connectorFramework"); + _connectorInfoManager = Assertions.NullChecked(defaultConnectorInfoManager, "connectorInfoManager"); + _handshakeMessage = new PRB.HandshakeMessage + { + SessionId = Guid.NewGuid().ToString(), + ServerType = PRB.HandshakeMessage.Types.ServerType.DOTNET + }; + } + + public virtual void OnClose(WebSocketConnectionHolder socket, int code, string reason) + { + Trace.TraceInformation("{0} onClose({1},{2}) ", LoggerName(), Convert.ToString(code), + Convert.ToString(reason)); + } + + public virtual void OnConnect(WebSocketConnectionHolder socket) + { + if (Client) + { + Trace.TraceInformation("Client onConnect() - Send 'HandshakeMessage({0})'", + _handshakeMessage.SessionId); + PRB.RemoteMessage requestBuilder = MessagesUtil.CreateRequest(0, + new PRB.RPCRequest + { + HandshakeMessage = _handshakeMessage + }); + socket.SendBytesAsync(requestBuilder.ToByteArray(), CancellationToken.None); + } + else + { + Trace.TraceInformation("Server onConnect()"); + } + } + + public virtual void OnError(Exception t) + { + //traceSource.TraceVerbose("Socket error {0}", t); + TraceUtil.TraceException("Socket error", t); + } + + public virtual void OnMessage(WebSocketConnectionHolder socket, string data) + { + Trace.TraceWarning("String message is ignored: {0}", data); + } + + public virtual void OnMessage(WebSocketConnectionHolder socket, byte[] bytes) + { +#if DEBUG + Debug.WriteLine("{0} onMessage(socket[{1}], {2}:bytes)", LoggerName(), socket.Id, bytes.Length); +#else + Trace.TraceInformation("{0} onMessage({1}:bytes)", LoggerName(), bytes.Length); +#endif + try + { + PRB.RemoteMessage message = PRB.RemoteMessage.Parser.ParseFrom(bytes); + + if (null != message.Request) + { + if (null != message.Request.HandshakeMessage) + { + if (Client) + { + Trace.TraceInformation("Error = The client must send the Handshake first"); + } + else + { + ProcessHandshakeRequest(socket, message.MessageId, message.Request.HandshakeMessage); + } + } + else if (socket.HandHooked) + { + if (null != message.Request.OperationRequest) + { + ProcessOperationRequest(socket, message.MessageId, message.Request.OperationRequest); + } + else if (null != message.Request.CancelOpRequest) + { + ProcessCancelOpRequest(socket, message.MessageId, message.Request.CancelOpRequest); + } + else if (null != message.Request.ControlRequest) + { + ProcessControlRequest(socket, message.MessageId, message.Request.ControlRequest); + } + else + { + HandleRequestMessage(socket, message.MessageId, message.Request); + } + } + else + { + HandleRequestMessage(socket, message.MessageId, message.Request); + } + } + else if (null != message.Response) + { + if (null != message.Response.HandshakeMessage) + { + if (Client) + { + ProcessHandshakeResponse(socket, message.MessageId, message.Response.HandshakeMessage); + } + else + { + Trace.TraceInformation("Error = The server must send the Handshake response"); + } + } + else if (null != message.Response.Error) + { + ProcessExceptionMessage(socket, message.MessageId, message.Response.Error); + } + else if (socket.HandHooked) + { + if (null != message.Response.OperationResponse) + { + ProcessOperationResponse(socket, message.MessageId, message.Response.OperationResponse); + } + else if (null != message.Response.ControlResponse) + { + ProcessControlResponse(socket, message.MessageId, message.Response.ControlResponse); + } + else + { + HandleResponseMessage(socket, message.MessageId, message.Response); + } + } + else + { + HandleResponseMessage(socket, message.MessageId, message.Response); + } + } + else + { + HandleRemoteMessage(socket, message); + } + } + catch (InvalidProtocolBufferException e) + { + Trace.TraceWarning("{0} failed parse message {1}", LoggerName(), e); + } + catch (Exception t) + { + Trace.TraceInformation("{0} Unhandled exception {1}", LoggerName(), t); + } + } + + protected virtual void HandleRemoteMessage(WebSocketConnectionHolder socket, PRB.RemoteMessage message) + { + if (socket.HandHooked) + { + socket.RemoteConnectionContext.RemoteConnectionGroup.TrySendMessage( + MessagesUtil.CreateErrorResponse(message.MessageId, new ConnectorException("Unknown RemoteMessage"))); + } + } + + protected virtual void HandleRequestMessage(WebSocketConnectionHolder socket, long messageId, + PRB.RPCRequest message) + { + if (socket.HandHooked) + { + socket.RemoteConnectionContext.RemoteConnectionGroup.TrySendMessage( + MessagesUtil.CreateErrorResponse(messageId, new ConnectorException("Unknown Request message"))); + } + else + { + socket.SendBytesAsync( + MessagesUtil.CreateErrorResponse(messageId, + new ConnectorException("Connection received request before handshake")).ToByteArray(), + CancellationToken.None); + } + } + + protected internal virtual void HandleResponseMessage(WebSocketConnectionHolder socket, long messageId, + PRB.RPCResponse message) + { + if (socket.HandHooked) + { + socket.RemoteConnectionContext.RemoteConnectionGroup.TrySendMessage( + MessagesUtil.CreateErrorResponse(messageId, new ConnectorException("Unknown Request message"))); + } + else + { + socket.SendBytesAsync( + MessagesUtil.CreateErrorResponse(messageId, + new ConnectorException("Connection received response before handshake")).ToByteArray(), + CancellationToken.None); + } + } + + public virtual void OnPing(WebSocketConnectionHolder socket, byte[] bytes) + { + // Nothing to do, pong response has been sent + Trace.TraceInformation("{0} onPing()", LoggerName()); + try + { +#if DEBUG + PRB.PingMessage message = PRB.PingMessage.Parser.ParseFrom(bytes); +#endif + } + catch (InvalidProtocolBufferException e) + { + Trace.TraceWarning("{0} failed parse message {1}", LoggerName(), e); + } + } + + public virtual void OnPong(WebSocketConnectionHolder socket, byte[] bytes) + { + // Confirm ping response! + Trace.TraceInformation("{0} onPong()", LoggerName()); + try + { +#if DEBUG + PRB.PingMessage message = PRB.PingMessage.Parser.ParseFrom(bytes); +#endif + } + catch (InvalidProtocolBufferException e) + { + Trace.TraceWarning("{0} failed parse message {1}", LoggerName(), e); + } + } + + protected internal virtual bool Client { get; private set; } + + // Handshake Operations + + public virtual void ProcessHandshakeRequest(WebSocketConnectionHolder socket, long messageId, + PRB.HandshakeMessage message) + { + PRB.RemoteMessage responseBuilder = MessagesUtil.CreateResponse(messageId, new + PRB.RPCResponse { HandshakeMessage = _handshakeMessage }); + socket.SendBytesAsync(responseBuilder.ToByteArray(), CancellationToken.None); + // Set Operational + socket.ReceiveHandshake(message); + Trace.TraceInformation("{0} accept Handshake ({1})", LoggerName(), message.SessionId); + } + + public virtual void ProcessHandshakeResponse(WebSocketConnectionHolder socket, long messageId, + PRB.HandshakeMessage message) + { + // Set Operational + socket.ReceiveHandshake(message); + + Trace.TraceInformation("{0} accept Handshake ({1})", LoggerName(), message.SessionId); + } + + // Control Operations + + public virtual void ProcessControlRequest(WebSocketConnectionHolder socket, long messageId, + PRB.ControlRequest message) + { + PRB.ControlResponse builder = new PRB.ControlResponse(); + if (message.InfoLevel.Contains(PRB.ControlRequest.Types.InfoLevel.CONNECTOR_INFO)) + { + if (_connectorInfoManager.ConnectorInfos.Any()) + { + IList connectorInfos = + _connectorInfoManager.ConnectorInfos.Select( + ci => toRemote((AbstractConnectorInfo)ci) + ).ToList(); + ByteString response = MessagesUtil.SerializeLegacy(connectorInfos); + builder.ConnectorInfos = response; + } + } + socket.RemoteConnectionContext.RemoteConnectionGroup.ProcessControlRequest(message); + PRB.RemoteMessage responseBuilder = MessagesUtil.CreateResponse(messageId, + new PRB.RPCResponse + { + ControlResponse = builder + }); + + socket.SendBytesAsync(responseBuilder.ToByteArray(), CancellationToken.None); + Trace.TraceInformation("{0} accept ControlRequest ({1})", LoggerName(), message); + } + + private Org.IdentityConnectors.Framework.Impl.Api.Remote.RemoteConnectorInfoImpl toRemote( + AbstractConnectorInfo source) + { + Org.IdentityConnectors.Framework.Impl.Api.Remote.RemoteConnectorInfoImpl rv = new Org.IdentityConnectors. + Framework.Impl.Api.Remote.RemoteConnectorInfoImpl + { + ConnectorDisplayNameKey = source.ConnectorDisplayNameKey, + ConnectorKey = source.ConnectorKey, + DefaultAPIConfiguration = source.DefaultAPIConfiguration, + Messages = source.Messages + }; + return rv; + } + + public virtual void ProcessControlResponse(WebSocketConnectionHolder socket, long messageId, + PRB.ControlResponse message) + { + socket.RemoteConnectionContext.RemoteConnectionGroup.ReceiveRequestResponse(socket, messageId, message); + + Trace.TraceInformation("{0} accept ControlResponse", LoggerName()); + } + + public virtual void ProcessOperationRequest(WebSocketConnectionHolder socket, long messageId, + PRB.OperationRequest message) + { + Debug.WriteLine("IN Request({0}:{1})", messageId, + socket.RemoteConnectionContext.RemotePrincipal.Identity.Name); + + String connectorFacadeKey = message.ConnectorFacadeKey.ToStringUtf8(); + + if (null != message.ConfigurationChangeEvent) + { + ICollection rawChanges = + MessagesUtil.DeserializeLegacy>(message.ConfigurationChangeEvent + .ConfigurationPropertyChange); + IList changes = + CollectionUtil.NewList(rawChanges); + socket.RemoteConnectionContext.RemoteConnectionGroup + .NotifyConfigurationChangeListener(connectorFacadeKey, changes); + } + else + { + Org.ForgeRock.OpenICF.Common.ProtoBuf.ConnectorKey connectorKey = message.ConnectorKey; + + API.ConnectorInfo info = FindConnectorInfo(connectorKey); + if (info == null) + { + PRB.RemoteMessage response = MessagesUtil.CreateErrorResponse(messageId, + new ConnectorException("Connector not found: " + connectorKey + " ")); + socket.RemoteConnectionContext.RemoteConnectionGroup.TrySendMessage(response); + return; + } + + try + { + try + { + if (null != message.Locale) + { + CultureInfo local = MessagesUtil.DeserializeMessage(message.Locale); + Thread.CurrentThread.CurrentUICulture = local; + } + } + catch (Exception e) + { + TraceUtil.TraceException("Failed to set request CultureInfo", e); + } + + API.ConnectorFacade connectorFacade = NewInstance(socket, info, connectorFacadeKey); + + if (null != message.AuthenticateOpRequest) + { + AuthenticationAsyncApiOpImpl.CreateProcessor(messageId, socket, message.AuthenticateOpRequest) + .Execute(connectorFacade); + } + else if (null != message.CreateOpRequest) + { + CreateAsyncApiOpImpl.CreateProcessor(messageId, socket, message.CreateOpRequest) + .Execute(connectorFacade); + } + else if (null != message.ConnectorEventSubscriptionOpRequest) + { + ConnectorEventSubscriptionApiOpImpl.CreateProcessor(messageId, socket, + message.ConnectorEventSubscriptionOpRequest).Execute(connectorFacade); + } + else if (null != message.DeleteOpRequest) + { + DeleteAsyncApiOpImpl.CreateProcessor(messageId, socket, message.DeleteOpRequest) + .Execute(connectorFacade); + } + else if (null != message.GetOpRequest) + { + GetAsyncApiOpImpl.CreateProcessor(messageId, socket, message.GetOpRequest) + .Execute(connectorFacade); + } + else if (null != message.ResolveUsernameOpRequest) + { + ResolveUsernameAsyncApiOpImpl.CreateProcessor(messageId, socket, + message.ResolveUsernameOpRequest) + .Execute(connectorFacade); + } + else if (null != message.SchemaOpRequest) + { + SchemaAsyncApiOpImpl.CreateProcessor(messageId, socket, message.SchemaOpRequest) + .Execute(connectorFacade); + } + else if (null != message.ScriptOnConnectorOpRequest) + { + ScriptOnConnectorAsyncApiOpImpl.CreateProcessor(messageId, socket, + message.ScriptOnConnectorOpRequest).Execute(connectorFacade); + } + else if (null != message.ScriptOnResourceOpRequest) + { + ScriptOnResourceAsyncApiOpImpl.CreateProcessor(messageId, socket, + message.ScriptOnResourceOpRequest) + .Execute(connectorFacade); + } + else if (null != message.SearchOpRequest) + { + SearchAsyncApiOpImpl.CreateProcessor(messageId, socket, message.SearchOpRequest) + .Execute(connectorFacade); + } + else if (null != message.SyncOpRequest) + { + SyncAsyncApiOpImpl.CreateProcessor(messageId, socket, message.SyncOpRequest) + .Execute(connectorFacade); + } + else if (null != message.SyncEventSubscriptionOpRequest) + { + SyncEventSubscriptionApiOpImpl.CreateProcessor(messageId, socket, + message.SyncEventSubscriptionOpRequest).Execute(connectorFacade); + } + else if (null != message.TestOpRequest) + { + TestAsyncApiOpImpl.CreateProcessor(messageId, socket, message.TestOpRequest) + .Execute(connectorFacade); + } + else if (null != message.UpdateOpRequest) + { + UpdateAsyncApiOpImpl.CreateProcessor(messageId, socket, message.UpdateOpRequest) + .Execute(connectorFacade); + } + else if (null != message.ValidateOpRequest) + { + ValidateAsyncApiOpImpl.CreateProcessor(messageId, socket, message.ValidateOpRequest) + .Execute(connectorFacade); + } + else + { + socket.RemoteConnectionContext.RemoteConnectionGroup.TrySendMessage( + MessagesUtil.CreateErrorResponse(messageId, + new ConnectorException("Unknown OperationRequest"))); + } + } + catch (Exception t) + { + TraceUtil.TraceException("Failed handle OperationRequest " + messageId, t); + socket.RemoteConnectionContext.RemoteConnectionGroup.TrySendMessage( + MessagesUtil.CreateErrorResponse(messageId, t)); + } + } + } + + public virtual void ProcessOperationResponse(WebSocketConnectionHolder socket, long messageId, + PRB.OperationResponse message) + { + Debug.WriteLine("IN Response({0}:{1})", messageId, + socket.RemoteConnectionContext.RemotePrincipal.Identity.Name); + socket.RemoteConnectionContext.RemoteConnectionGroup.ReceiveRequestResponse(socket, messageId, message); + } + + public virtual void ProcessExceptionMessage(WebSocketConnectionHolder socket, long messageId, + PRB.ExceptionMessage message) + { + socket.RemoteConnectionContext.RemoteConnectionGroup.ReceiveRequestResponse(socket, messageId, message); + } + + public virtual void ProcessCancelOpRequest(WebSocketConnectionHolder socket, long messageId, + PRB.CancelOpRequest message) + { + socket.RemoteConnectionContext.RemoteConnectionGroup.ReceiveRequestCancel(messageId); + } + + /* + protected internal virtual Encryptor initialiseEncryptor() + { + HandshakeMessage message = null; + // Create Encryptor + if (message.hasPublicKey()) + { + PublicKey publicKey = SecurityUtil.createPublicKey(message.PublicKey.toByteArray()); + try + { + Encryptor encryptor = new ECIESEncryptor(keyPair.Private, publicKey); + } + catch (InvalidKeyException) + { + sbyte[] error = null; + // socket.sendBytes(error); + } + } + return null; + }*/ + + protected internal virtual string LoggerName() + { + return Client ? "Client" : "Server"; + } + + public virtual API.ConnectorFacade NewInstance(WebSocketConnectionHolder socket, API.ConnectorInfo connectorInfo, + string config) + { + return _connectorFramework.NewManagedInstance(connectorInfo, config, + new RemoteConfigurationChangeListener(socket, connectorInfo, config)); + } + + public virtual API.ConnectorInfo FindConnectorInfo(Org.ForgeRock.OpenICF.Common.ProtoBuf.ConnectorKey key) + { + return + _connectorInfoManager.FindConnectorInfo(new API.ConnectorKey(key.BundleName, key.BundleVersion, + key.ConnectorName)); + } + + private class RemoteConfigurationChangeListener : API.IConfigurationPropertyChangeListener + { + private readonly WebSocketConnectionHolder socket; + private readonly API.ConnectorInfo connectorInfo; + private readonly string config; + + public RemoteConfigurationChangeListener(WebSocketConnectionHolder socket, API.ConnectorInfo connectorInfo, + string config) + { + this.socket = socket; + this.connectorInfo = connectorInfo; + this.config = config; + } + + public virtual void ConfigurationPropertyChange(IList changes) + { + try + { + PRB.RemoteMessage request = MessagesUtil.CreateRequest(0, new PRB.RPCRequest + { + OperationRequest = new PRB.OperationRequest + { + ConnectorFacadeKey = ByteString.CopyFromUtf8(config), + ConfigurationChangeEvent = new PRB.ConfigurationChangeEvent + { + ConfigurationPropertyChange = MessagesUtil.SerializeLegacy(changes) + } + } + }); + + socket.RemoteConnectionContext.RemoteConnectionGroup.TrySendMessage(request); + } + catch (Exception t) + { + TraceUtil.TraceException("Failed to send ConfigurationChangeEvent event: " + connectorInfo, t); + } + } + } + } + + #endregion + + #region ReferenceCountedObject + + /// + /// An object which is lazily created when first referenced, and destroyed when + /// the last reference is released. + /// + /// The type of referenced object. + public abstract class ReferenceCountedObject + { + /// + /// A reference to the reference counted object which will automatically be + /// released during garbage collection. + /// + public sealed class Reference + { + private readonly ReferenceCountedObject _outerInstance; + + /// + /// The value will be accessed by the finalizer thread so it needs to be + /// volatile in order to ensure that updates are published. + /// + internal TR Value; + + internal Reference(ReferenceCountedObject outerInstance, TR value) + { + _outerInstance = outerInstance; + Value = value; + } + + /// + /// Returns the referenced object. + /// + /// The referenced object. + /// + /// If the referenced object has already been released. + /// + public TR Get() + { + if (Value == null) + { + throw new NullReferenceException(); // Fail-fast. + } + return Value; + } + + /// + /// Decrements the reference count for the reference counted object if + /// this reference refers to the reference counted instance. If the + /// reference count drops to zero then the referenced object will be + /// destroyed. + /// + public void Release() + { + TR instanceToRelease = default(TR); + lock (_outerInstance._lock) + { + if (Value != null) + { + if (Value.Equals(_outerInstance._instance) && --_outerInstance._refCount == 0) + { + // This was the last reference. + instanceToRelease = Value; + _outerInstance._instance = default(TR); + } + + /* + * Force NPE for subsequent get() attempts and prevent + * multiple releases. + */ + Value = default(TR); + } + } + if (instanceToRelease != null) + { + _outerInstance.DestroyInstance(instanceToRelease); + } + } + + /// + /// Provide a finalizer because reference counting is intended for + /// expensive rarely created resources which should not be accidentally + /// left around. + /// + ~Reference() + { + Release(); + } + } + + private T _instance; + private readonly object _lock = new object(); + private int _refCount; + + /// + /// Creates a new referenced object whose reference count is initially zero. + /// + protected internal ReferenceCountedObject() + { + // Nothing to do. + } + + /// + /// Returns a reference to the reference counted object. + /// + /// A reference to the reference counted object. + public Reference Acquire() + { + lock (_lock) + { + if (_refCount++ == 0) + { + Debug.Assert(_instance == null); + _instance = NewInstance(); + } + Debug.Assert(_instance != null); + return new Reference(this, _instance); + } + } + + protected internal virtual bool Null + { + get { return _instance == null; } + } + + /// + /// Returns a reference to the provided object or, if it is {@code null}, a + /// reference to the reference counted object. + /// + /// + /// The object to be referenced, or {@code null} if the reference + /// counted object should be used. + /// + /// + /// A reference to the provided object or, if it is {@code null}, a + /// reference to the reference counted object. + /// + public Reference AcquireIfNull(T value) + { + return value != null ? new Reference(this, value) : Acquire(); + } + + /// + /// Invoked when a reference is released and the reference count will become + /// zero. Implementations should release any resources associated with the + /// resource and should not return until the resources have been released. + /// + /// + /// The instance to be destroyed. + /// + protected internal abstract void DestroyInstance(T instance); + + /// + /// Invoked when a reference is acquired and the current reference count is + /// zero. Implementations should create a new instance as fast as possible. + /// + /// The new instance. + protected internal abstract T NewInstance(); + } + + #endregion + + #region RemoteAsyncConnectorFacade + + public class RemoteAsyncConnectorFacade : AbstractConnectorFacade, IAsyncConnectorFacade + { + private readonly ConcurrentDictionary _facadeKeys; + + private readonly IAuthenticationAsyncApiOp _authenticationApiOp; + private readonly ICreateAsyncApiOp _createApiOp; + private readonly IConnectorEventSubscriptionApiOp _connectorEventSubscriptionApiOp; + private readonly IDeleteAsyncApiOp _deleteApiOp; + private readonly IGetAsyncApiOp _getApiOp; + private readonly IResolveUsernameAsyncApiOp _resolveUsernameApiOp; + private readonly ISchemaAsyncApiOp _schemaApiOp; + private readonly IScriptOnConnectorAsyncApiOp _scriptOnConnectorApiOp; + private readonly IScriptOnResourceAsyncApiOp _scriptOnResourceApiOp; + private readonly SearchApiOp _searchApiOp; + private readonly SyncApiOp _syncApiOp; + private readonly ISyncEventSubscriptionApiOp _syncEventSubscriptionApiOp; + private readonly ITestAsyncApiOp _testApiOp; + private readonly IUpdateAsyncApiOp _updateApiOp; + private readonly IValidateAsyncApiOp _validateApiOp; + + + private class InnerFacadeContext : ILoadBalancingConnectorFacadeContext + { + private readonly API.ConnectorInfo _connectorInfo; + private readonly RemoteOperationContext _context; + + public InnerFacadeContext(API.ConnectorInfo connectorInfo, RemoteOperationContext context) + { + _connectorInfo = connectorInfo; + _context = context; + } + + public API.APIConfiguration ApiConfiguration + { + get { return _connectorInfo.CreateDefaultAPIConfiguration(); } + } + + public string PrincipalName + { + get { return _context.RemotePrincipal.Identity.Name; } + } + + public RemoteOperationContext RemoteOperationContext + { + get { return _context; } + } + } + + protected internal RemoteAsyncConnectorFacade(APIConfigurationImpl configuration, + Func transformer) + : base(configuration) + { + if (configuration.ConnectorInfo is RemoteConnectorInfoImpl) + { + IRequestDistributor + remoteConnection = + Assertions.NullChecked( + ((RemoteConnectorInfoImpl)configuration.ConnectorInfo).messageDistributor, + "messageDistributor"); + + API.ConnectorKey connectorKey = GetAPIConfiguration().ConnectorInfo.ConnectorKey; + Func facadeKeyFunction; + if (null != transformer) + { + _facadeKeys = new ConcurrentDictionary(); + facadeKeyFunction = (value) => + { + ByteString facadeKey; + _facadeKeys.TryGetValue(value.RemotePrincipal.Identity.Name, out facadeKey); + if (null == facadeKey) + { + API.ConnectorInfo connectorInfo = value.RemoteConnectionGroup.FindConnectorInfo(connectorKey); + if (null != connectorInfo) + { + // Remote server has the ConnectorInfo + try + { + API.APIConfiguration fullConfiguration = + transformer( + new InnerFacadeContext(connectorInfo, value)); + if (null != fullConfiguration) + { + string connectorFacadeKey = + (SerializerUtil.SerializeBase64Object(fullConfiguration)); + facadeKey = ByteString.CopyFromUtf8(connectorFacadeKey); + if (null != GetAPIConfiguration().ChangeListener) + { + value.RemoteConnectionGroup.AddConfigurationChangeListener( + connectorFacadeKey, + GetAPIConfiguration().ChangeListener); + } + _facadeKeys.TryAdd(value.RemotePrincipal.Identity.Name, facadeKey); + } + } + catch (Exception t) + { + TraceUtil.TraceException(TraceLevel.Warning, + "Failed to build APIConfiguration for {0}", t, + value.RemotePrincipal.Identity.Name); + } + } + else + { + Trace.TraceInformation( + "Can not execute Operation on {0} because ConnectorInfo [{1}] is not installed", + value.RemotePrincipal.Identity.Name, connectorKey); + } + } + return facadeKey; + }; + } + else + { + _facadeKeys = null; + ByteString facadeKey = ByteString.CopyFromUtf8(ConnectorFacadeKey); + facadeKeyFunction = context => + { + context.RemoteConnectionGroup.FindConnectorInfo(GetAPIConfiguration().ConnectorInfo.ConnectorKey); + if (null != GetAPIConfiguration().ChangeListener) + { + context.RemoteConnectionGroup.AddConfigurationChangeListener(ConnectorFacadeKey, + GetAPIConfiguration().ChangeListener); + } + return facadeKey; + }; + } + + // initialise operations + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _authenticationApiOp = CreateLogging(SafeType.Get(), new AuthenticationAsyncApiOpImpl(remoteConnection, connectorKey, + facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _authenticationApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _createApiOp = CreateLogging(SafeType.Get(), new CreateAsyncApiOpImpl(remoteConnection, connectorKey, facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _createApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _connectorEventSubscriptionApiOp = CreateLogging(SafeType.Get(), new ConnectorEventSubscriptionApiOpImpl(remoteConnection, + connectorKey, + facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _connectorEventSubscriptionApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _deleteApiOp = CreateLogging(SafeType.Get(), new DeleteAsyncApiOpImpl(remoteConnection, connectorKey, facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _deleteApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _resolveUsernameApiOp = CreateLogging(SafeType.Get(), new ResolveUsernameAsyncApiOpImpl(remoteConnection, connectorKey, + facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _resolveUsernameApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _schemaApiOp = CreateLogging(SafeType.Get(), new SchemaAsyncApiOpImpl(remoteConnection, connectorKey, facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _schemaApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _scriptOnConnectorApiOp = CreateLogging(SafeType.Get(), new ScriptOnConnectorAsyncApiOpImpl(remoteConnection, connectorKey, + facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _scriptOnConnectorApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _scriptOnResourceApiOp = CreateLogging(SafeType.Get(), new ScriptOnResourceAsyncApiOpImpl(remoteConnection, connectorKey, + facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _scriptOnResourceApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _searchApiOp = CreateLogging(SafeType.Get(), new SearchAsyncApiOpImpl(remoteConnection, connectorKey, facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + _getApiOp = CreateLogging(SafeType.Get(), new GetAsyncApiOpImpl(remoteConnection, connectorKey, facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _searchApiOp = null; + _getApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _syncApiOp = CreateLogging(SafeType.Get(), new SyncAsyncApiOpImpl(remoteConnection, connectorKey, facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _syncApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _syncEventSubscriptionApiOp = CreateLogging(SafeType.Get(), new SyncEventSubscriptionApiOpImpl(remoteConnection, connectorKey, + facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _syncEventSubscriptionApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _testApiOp = CreateLogging(SafeType.Get(), new TestAsyncApiOpImpl(remoteConnection, connectorKey, facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _testApiOp = null; + } + if (configuration.IsSupportedOperation(SafeType.Get())) + { + _updateApiOp = CreateLogging(SafeType.Get(), new UpdateAsyncApiOpImpl(remoteConnection, connectorKey, facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + _updateApiOp = null; + } + _validateApiOp = CreateLogging(SafeType.Get(), new ValidateAsyncApiOpImpl(remoteConnection, connectorKey, facadeKeyFunction, GetAPIConfiguration().GetTimeout(SafeType.Get()))); + } + else + { + throw new InvalidParameterException("Unsupported ConnectorInfo type"); + } + } + + + protected T CreateLogging(SafeType api, T target) + { + T ret = target; + if (LOGGINGPROXY_ENABLED) + { + ret = (T)Proxy.NewProxyInstance(typeof(T), new LoggingProxy(api, target)); + } + return ret; + } + + public RemoteAsyncConnectorFacade(RemoteConnectorInfoImpl firstConnectorInfo, + Func transformer) + : this((APIConfigurationImpl)firstConnectorInfo.CreateDefaultAPIConfiguration(), transformer) + { + } + + public RemoteAsyncConnectorFacade(APIConfigurationImpl configuration) + : this(configuration, null) + { + } + + protected internal virtual T GetAsyncOperationCheckSupported() where T : APIOperation + { + T op = (T)GetOperationImplementation(SafeType.ForRawType(typeof(T))); + + // check if this operation is supported. + if (null == op) + { + throw new NotSupportedException(String.Format(Msg, typeof(T))); + } + return op; + } + + protected override APIOperation GetOperationImplementation(SafeType api) + { + if (typeof(AuthenticationApiOp).IsAssignableFrom(api.RawType)) + { + return _authenticationApiOp; + } + if (typeof(CreateApiOp).IsAssignableFrom(api.RawType)) + { + return _createApiOp; + } + if (typeof(IConnectorEventSubscriptionApiOp).IsAssignableFrom(api.RawType)) + { + return _connectorEventSubscriptionApiOp; + } + if (typeof(DeleteApiOp).IsAssignableFrom(api.RawType)) + { + return _deleteApiOp; + } + if (typeof(GetApiOp).IsAssignableFrom(api.RawType)) + { + return _getApiOp; + } + if (typeof(ResolveUsernameApiOp).IsAssignableFrom(api.RawType)) + { + return _resolveUsernameApiOp; + } + if (typeof(SchemaApiOp).IsAssignableFrom(api.RawType)) + { + return _schemaApiOp; + } + if (typeof(ScriptOnConnectorApiOp).IsAssignableFrom(api.RawType)) + { + return _scriptOnConnectorApiOp; + } + if (typeof(ScriptOnResourceApiOp).IsAssignableFrom(api.RawType)) + { + return _scriptOnResourceApiOp; + } + if (typeof(SearchApiOp).IsAssignableFrom(api.RawType)) + { + return _searchApiOp; + } + if (typeof(SyncApiOp).IsAssignableFrom(api.RawType)) + { + return _syncApiOp; + } + if (typeof(ISyncEventSubscriptionApiOp).IsAssignableFrom(api.RawType)) + { + return _syncEventSubscriptionApiOp; + } + if (typeof(TestApiOp).IsAssignableFrom(api.RawType)) + { + return _testApiOp; + } + if (typeof(UpdateApiOp).IsAssignableFrom(api.RawType)) + { + return _updateApiOp; + } + if (typeof(ValidateApiOp).IsAssignableFrom(api.RawType)) + { + return _validateApiOp; + } + return null; + } + + public async Task AuthenticateAsync( + OBJ.ObjectClass objectClass, + string username, GuardedString password, OBJ.OperationOptions options, CancellationToken cancellationToken) + { + return await GetAsyncOperationCheckSupported() + .AuthenticateAsync(objectClass, username, password, options, cancellationToken); + } + + public async Task CreateAsync(OBJ.ObjectClass objectClass, + ICollection createAttributes, OBJ.OperationOptions options, + CancellationToken cancellationToken) + { + return await GetAsyncOperationCheckSupported() + .CreateAsync(objectClass, createAttributes, options, cancellationToken); + } + + public async Task DeleteAsync(OBJ.ObjectClass objectClass, IdentityConnectors.Framework.Common.Objects.Uid uid, + OBJ.OperationOptions options, CancellationToken cancellationToken) + { + await + GetAsyncOperationCheckSupported() + .DeleteAsync(objectClass, uid, options, cancellationToken); + } + + public async Task GetObjectAsync(OBJ.ObjectClass objectClass, + IdentityConnectors.Framework.Common.Objects.Uid uid, OBJ.OperationOptions options, + CancellationToken cancellationToken) + { + return + await + GetAsyncOperationCheckSupported() + .GetObjectAsync(objectClass, uid, options, cancellationToken); + } + + public async Task ResolveUsernameAsync( + OBJ.ObjectClass objectClass, + string username, OBJ.OperationOptions options, CancellationToken cancellationToken) + { + return await GetAsyncOperationCheckSupported() + .ResolveUsernameAsync(objectClass, username, options, cancellationToken); + } + + public async Task SchemaAsync(CancellationToken cancellationToken) + { + return await GetAsyncOperationCheckSupported().SchemaAsync(cancellationToken); + } + + public async Task RunScriptOnConnectorAsync(OBJ.ScriptContext request, OBJ.OperationOptions options, + CancellationToken cancellationToken) + { + return await GetAsyncOperationCheckSupported() + .RunScriptOnConnectorAsync(request, options, cancellationToken); + } + + public async Task RunScriptOnResourceAsync(OBJ.ScriptContext request, OBJ.OperationOptions options, + CancellationToken cancellationToken) + { + return await GetAsyncOperationCheckSupported() + .RunScriptOnResourceAsync(request, options, cancellationToken); + } + + public Task TestAsync(CancellationToken cancellationToken) + { + return GetAsyncOperationCheckSupported() + .TestAsync(cancellationToken); + } + + public Task UpdateAsync(OBJ.ObjectClass objectClass, + IdentityConnectors.Framework.Common.Objects.Uid uid, ICollection replaceAttributes, + OBJ.OperationOptions options, CancellationToken cancellationToken) + { + return GetAsyncOperationCheckSupported() + .UpdateAsync(objectClass, uid, replaceAttributes, options, cancellationToken); + } + + public Task AddAttributeValuesAsync( + OBJ.ObjectClass objectClass, + IdentityConnectors.Framework.Common.Objects.Uid uid, ICollection valuesToAdd, + OBJ.OperationOptions options, CancellationToken cancellationToken) + { + return GetAsyncOperationCheckSupported() + .AddAttributeValuesAsync(objectClass, uid, valuesToAdd, options, cancellationToken); + } + + public Task RemoveAttributeValuesAsync( + OBJ.ObjectClass objectClass, + IdentityConnectors.Framework.Common.Objects.Uid uid, ICollection valuesToRemove, + OBJ.OperationOptions options, CancellationToken cancellationToken) + { + return GetAsyncOperationCheckSupported() + .RemoveAttributeValuesAsync(objectClass, uid, valuesToRemove, options, cancellationToken); + } + + public Task ValidateAsync(CancellationToken cancellationToken) + { + return GetAsyncOperationCheckSupported() + .ValidateAsync(cancellationToken); + } + } + + #endregion + + #region RemoteConnectorInfoImpl + + public class RemoteConnectorInfoImpl : AbstractConnectorInfo + { + internal readonly + IRequestDistributor + messageDistributor; + + public RemoteConnectorInfoImpl( + IRequestDistributor + remoteConnection, AbstractConnectorInfo copyFrom) + { + messageDistributor = Assertions.NullChecked(remoteConnection, "remoteConnection"); + Assertions.NullCheck(copyFrom, "copyFrom"); + ConnectorDisplayNameKey = copyFrom.ConnectorDisplayNameKey; + ConnectorKey = copyFrom.ConnectorKey; + Messages = copyFrom.Messages; + ConnectorCategoryKey = copyFrom.ConnectorCategoryKey; + DefaultAPIConfiguration = copyFrom.DefaultAPIConfiguration; + } + } + + #endregion + + #region RoundRobinLoadBalancingAlgorithmFactory + + public class RoundRobinLoadBalancingAlgorithmFactory : LoadBalancingAlgorithmFactory + { + protected override + IRequestDistributor + CreateLoadBalancer( + IList> + delegates) + { + return + new RoundRobinLoadBalancingAlgorithm + (delegates); + } + } + + #endregion + + #region SecurityUtil + + /// + /// + /// since 1.5 + public class SecurityUtil + { + public static AsymmetricCipherKeyPair GenerateKeyPair() + { + try + { + var ecP = CustomNamedCurves.GetByName(NamedCurve.secp256r1.ToString()); + var ecParams = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed()); + ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); + keyPairGenerator.Init(new ECKeyGenerationParameters(ecParams, new SecureRandom())); + return keyPairGenerator.GenerateKeyPair(); + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + Console.Write(e.StackTrace); + } + return null; + } + } + + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/Rpc.cs b/dotnet/framework/FrameworkServer/Rpc.cs new file mode 100755 index 00000000..ef90759d --- /dev/null +++ b/dotnet/framework/FrameworkServer/Rpc.cs @@ -0,0 +1,941 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net.WebSockets; +using System.Security.Principal; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Google.Protobuf; +using Org.ForgeRock.OpenICF.Common.ProtoBuf; +using Org.ForgeRock.OpenICF.Common.RPC; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Common; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Impl.Api; + +namespace Org.ForgeRock.OpenICF.Framework.Remote +{ + + #region LocalOperationProcessor + + public abstract class LocalOperationProcessor : + LocalRequest + { + private int _inconsistencyCounter = 0; + + protected WebSocketConnectionHolder ReverseConnection; + + protected internal LocalOperationProcessor(long requestId, WebSocketConnectionHolder socket) + : base(requestId, socket) + { + } + + protected abstract RPCResponse CreateOperationResponse(RemoteOperationContext remoteContext, + TV result); + + public override Boolean Check() + { + Boolean valid = _inconsistencyCounter < 3; + if (!valid) + { + Trace.TraceInformation( + "LocalRequest:{0} -> inconsistent with remote server, trying to cancel local process.", + RequestId); + Dispose(); + } + return valid; + } + + public override void Inconsistent() + { + _inconsistencyCounter++; + } + + protected override bool TryHandleResult(TV result) + { + try + { + byte[] responseMessage = + new RemoteMessage + { + MessageId = RequestId, + Response = CreateOperationResponse(RemoteConnectionContext, result) + }.ToByteArray(); + + return null != TrySendBytes(responseMessage); + } + catch (ConnectorIOException e) + { + // May not be complete / failed to send response + TraceUtil.TraceException("Operation complete successfully but failed to send result", e); + } + catch (Exception e) + { + TraceUtil.TraceException("Operation complete successfully but failed to build result message", e); + } + return false; + } + + protected override bool TryHandleError(Exception error) + { + byte[] responseMessage = MessagesUtil.CreateErrorResponse(RequestId, error).ToByteArray(); + try + { + return null != TrySendBytes(responseMessage, true); + } + catch (ConnectorIOException e) + { + TraceUtil.TraceException("Operation complete unsuccessfully and failed to send error", e); + } + catch (Exception e) + { + TraceUtil.TraceException("Operation complete unsuccessfully and failed to build result message", e); + } + return false; + } + + protected WebSocketConnectionHolder TrySendBytes(byte[] responseMessage) + { + return TrySendBytes(responseMessage, false); + } + + protected WebSocketConnectionHolder TrySendBytes(byte[] responseMessage, bool useAnyConnection) + { + return TrySendMessageNow(responseMessage); + } + + private WebSocketConnectionHolder TrySendMessageNow(byte[] responseMessage) + { + return + RemoteConnectionContext.RemoteConnectionGroup.TrySendMessage( + connection => + connection(new WebSocketConnectionHolder.InternalAsyncMessageQueueRecord(responseMessage)) + .Result); + } + + protected override bool TryCancel() + { + return false; + } + } + + #endregion + + #region RemoteOperationContext + + public class RemoteOperationContext : + IRemoteConnectionContext + { + private readonly WebSocketConnectionGroup _connectionGroup; + + private readonly IPrincipal _connectionPrincipal; + + protected internal RemoteOperationContext(IPrincipal connectionPrincipal, + WebSocketConnectionGroup connectionGroup) + { + _connectionPrincipal = connectionPrincipal; + _connectionGroup = connectionGroup; + } + + public virtual WebSocketConnectionGroup RemoteConnectionGroup + { + get { return _connectionGroup; } + } + + public virtual IPrincipal RemotePrincipal + { + get { return _connectionPrincipal; } + } + } + + #endregion + + #region RemoteOperationRequest + + public abstract class RemoteOperationRequest : + RemoteRequest + { + + protected int InconsistencyCounter = 0; + + protected RemoteOperationRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > + completionCallback, CancellationToken cancellationToken) + : base(context, requestId, completionCallback, cancellationToken) + { + } + + protected internal abstract bool HandleResponseMessage(WebSocketConnectionHolder sourceConnection, + Object message); + + protected internal abstract RPCRequest CreateOperationRequest(RemoteOperationContext remoteContext); + + public override Boolean Check() + { + Boolean valid = InconsistencyCounter < 3; + if (!valid) + { + Trace.TraceInformation( + "RemoteRequest:{0} -> inconsistent with remote server, set failed local process.", + RequestId); + HandleError( + new ConnectorException( + "Operation finished on remote server with unknown result")); + } + return valid; + } + + public override void Inconsistent() + { + InconsistencyCounter++; + } + + public override void HandleIncomingMessage(WebSocketConnectionHolder sourceConnection, Object message) + { + var exceptionMessage = message as ExceptionMessage; + if (exceptionMessage != null) + { + HandleExceptionMessage(exceptionMessage); + } + else + { + if (message != null) + { + if (!HandleResponseMessage(sourceConnection, message)) + { +#if DEBUG + Debug.WriteLine("Request {0} has unknown response message type:{1}", RequestId, this.GetType().Name); +#endif + HandleError(new ConnectorException("Unknown response message type:" + message.GetType())); + } + } + } + } + + protected override + RemoteConnectionGroup. + AsyncMessageQueueRecord CreateMessageElement(RemoteOperationContext remoteContext, long requestId) + { + return new WebSocketConnectionHolder.InternalAsyncMessageQueueRecord(new RemoteMessage + { + MessageId = requestId, + Request = CreateOperationRequest(remoteContext) + }.ToByteArray()); + } + + protected override void TryCancelRemote(RemoteOperationContext remoteContext, long requestId) + { + byte[] cancelMessage = + new RemoteMessage + { + MessageId = requestId, + Request = new RPCRequest + { + CancelOpRequest = new CancelOpRequest() + } + }.ToByteArray(); + + TrySendBytes(cancelMessage); + } + + protected override Exception CreateCancellationException(Exception cancellationException) + { + var canceledException = cancellationException as OperationCanceledException; + if (canceledException != null) + { + return cancellationException; + } + if (null != cancellationException) + { + OperationCanceledException exception = new OperationCanceledException(cancellationException.Message, + cancellationException); + return exception; + } + + return new OperationCanceledException("Operation is cancelled #" + RequestId); + } + + protected internal virtual void TrySendBytes(byte[] cancelMessage) + { + if (null == ConnectionContext.RemoteConnectionGroup.TrySendMessage(async connection => + { + await connection(new WebSocketConnectionHolder.InternalAsyncMessageQueueRecord(cancelMessage)); + return true; + })) + { + // Failed to send remote message + throw new ConnectorIOException("Transport layer is not operational"); + } + } + + protected internal virtual void HandleExceptionMessage(ExceptionMessage exceptionMessage) + { + try + { + HandleError(MessagesUtil.FromExceptionMessage(exceptionMessage)); + } + catch (Exception e) + { + Trace.TraceInformation("Exception received but failed to handle it: {0}:{1}", RequestId, e.Message); + } + } + } + + #endregion + + #region WebSocketConnectionGroup + + public class WebSocketConnectionGroup : + RemoteConnectionGroup, + IAsyncConnectorInfoManager + { + private DateTime _lastActivity = DateTime.Now; + + private readonly Encryptor _encryptor = null; + + private RemoteOperationContext _operationContext; + + private readonly HashSet _principals = new HashSet(StringComparer.OrdinalIgnoreCase); + + private readonly ConcurrentDictionary + _configurationChangeListeners = + new ConcurrentDictionary(); + + public delegate void WebSocketConnectionGroupEventListenerOnDispose(WebSocketConnectionHolder disposeable); + + private readonly EventHandler _closeListener = (sender, args) => + { + WebSocketConnectionHolder connection = sender as WebSocketConnectionHolder; + if (null != connection) + { + foreach (var p in connection.RemoteConnectionContext.RemoteConnectionGroup.WebSockets) + { + if (connection.Equals(p.Key)) + { + String ignore; + connection.RemoteConnectionContext.RemoteConnectionGroup.WebSockets.TryRemove(p.Key, out ignore); + } + } + } + }; + + private readonly RemoteConnectorInfoManager _delegate; + + public WebSocketConnectionGroup(string remoteSessionId) + : base(remoteSessionId) + { + _delegate = new RemoteConnectorInfoManager(this); + } + + public virtual RemoteOperationContext Handshake(IPrincipal connectionPrincipal, + WebSocketConnectionHolder webSocketConnection, HandshakeMessage message) + { + if (null == _operationContext) + { + lock (this) + { + if (null == _operationContext) + { + _operationContext = new RemoteOperationContext(connectionPrincipal, this); + } + } + } + if (RemoteSessionId.Equals(message.SessionId)) + { + WebSockets.TryAdd(webSocketConnection, connectionPrincipal.Identity.Name); + webSocketConnection.Disposed += _closeListener; + if (null != connectionPrincipal.Identity.Name) + _principals.Add(connectionPrincipal.Identity.Name); + //This is not thread-safe, it could yield true for cuncurrent threads + if (webSocketConnection.Equals(WebSockets.Keys.First())) + { + ControlMessageRequestFactory requestFactory = new ControlMessageRequestFactory(); + requestFactory.InfoLevels.Add(ControlRequest.Types.InfoLevel.CONNECTOR_INFO); + TrySubmitRequest(requestFactory); + } + } + return _operationContext; + } + + public virtual void PrincipalIsShuttingDown(IPrincipal connectionPrincipal) + { + string name = connectionPrincipal.Identity.Name; + if (_principals.Remove(name)) + { + Shutdown(); + foreach (var entry in WebSockets) + { + if (name.Equals(entry.Value, StringComparison.CurrentCultureIgnoreCase)) + { + String ignore; + WebSockets.TryRemove(entry.Key, out ignore); + } + } + } + } + + protected void Shutdown() + { + if (!_principals.Any()) + { + // Gracefully close all request and shut down this group. + foreach ( + var local in + LocalRequests.Select(entry => entry.Value) + .OfType + ()) + { + local.Dispose(); + } + foreach ( + var remote in + RemoteRequests.Select(entry => entry.Value) + .OfType + ()) + { + remote.Dispose(); + } + _delegate.Dispose(); + } + } + + public bool TrySendMessage(IMessage message) + { + byte[] messageBytes = message.ToByteArray(); + return true.Equals(TrySendMessage(connection => + { + connection(new WebSocketConnectionHolder.InternalAsyncMessageQueueRecord(messageBytes)).Wait(); + return true; + })); + } + + + protected override RemoteOperationContext RemoteConnectionContext + { + get { return _operationContext; } + } + + public override bool Operational + { + get { return WebSockets.Keys.Any(e => e.Operational); } + } + + public virtual Encryptor Encryptor + { + get { return _encryptor; } + } + + // --- AsyncConnectorInfoManager implementation --- + + public virtual Task FindConnectorInfoAsync(Org.IdentityConnectors.Framework.Api.ConnectorKey key) + { + return _delegate.FindConnectorInfoAsync(key); + } + + public virtual Task FindConnectorInfoAsync(ConnectorKeyRange keyRange) + { + return _delegate.FindConnectorInfoAsync(keyRange); + } + + public virtual IList ConnectorInfos + { + get { return _delegate.ConnectorInfos; } + } + + public virtual ConnectorInfo FindConnectorInfo(Org.IdentityConnectors.Framework.Api.ConnectorKey key) + { + return _delegate.FindConnectorInfo(key); + } + + // --- AsyncConnectorInfoManager implementation --- + + public void AddConfigurationChangeListener(String key, IConfigurationPropertyChangeListener listener) + { + if (!String.IsNullOrEmpty(key)) + { + if (null != listener) + { + _configurationChangeListeners.TryAdd(key, listener); + } + else + { + IConfigurationPropertyChangeListener ignore; + _configurationChangeListeners.TryRemove(key, out ignore); + } + } + } + + public void NotifyConfigurationChangeListener(string key, IList change) + { + if (null != key && null != change) + { + IConfigurationPropertyChangeListener listener; + _configurationChangeListeners.TryGetValue(key, out listener); + if (null != listener) + { + try + { + listener.ConfigurationPropertyChange(change); + } + catch (Exception e) + { +#if DEBUG + StringBuilder builder = new StringBuilder("Failed to notify connfiguration change - "); + TraceUtil.ExceptionToString(builder, e, String.Empty); + Debug.WriteLine(builder.ToString()); +#endif + } + } + } + } + + public virtual bool CheckIsActive() + { + bool operational = Operational; + + if (new TimeSpan(DateTime.Now.Ticks - _lastActivity.Ticks).TotalHours > 0 && !Operational) + { + // 1 hour inactivity -> Shutdown + _principals.Clear(); + Shutdown(); + WebSockets.Clear(); + } + else + { + foreach (var local in LocalRequests.Values) + { + local.Check(); + } + + foreach (var remote in RemoteRequests.Values) + { + remote.Check(); + } + + if (operational) + { + ControlMessageRequestFactory requestFactory = new ControlMessageRequestFactory(); + TrySubmitRequest(requestFactory); + } + } + return operational || !RemoteRequests.IsEmpty || !LocalRequests.IsEmpty; + } + + public void ProcessControlRequest(ControlRequest message) + { + _lastActivity = DateTime.Now; + foreach (var entry in RemoteRequests) + { + if (!message.LocalRequestId.Contains(entry.Key)) + { + //Remote request is exists locally but remotely nothing match. + // 1. ControlRequest was sent before LocalRequest was created so it normal. + // 2. Request on remote side is completed so we must Complete the RemoteRequest(Fail) unless the local request is still processing. + entry.Value.Inconsistent(); + } + } + + foreach (var entry in LocalRequests) + { + if (!message.RemoteRequestId.Contains(entry.Key)) + { + //Local request exists locally but remotely nothing match. + // 1. ControlRequest was sent before RemoteRequest was created so it normal. + // 2. Request on remote side is Cancelled/Terminated so it can safely Cancel here. + entry.Value.Inconsistent(); + } + } + } + + // -- Static Classes + + private sealed class RemoteConnectorInfoManager : + ManagedAsyncConnectorInfoManager + { + private readonly + IRequestDistributor + _outerInstance; + + public RemoteConnectorInfoManager( + IRequestDistributor + remoteConnection) + { + _outerInstance = remoteConnection; + } + + internal void AddOtherConnectorInfo(T1 connectorInfo) where T1 : AbstractConnectorInfo + { + AddConnectorInfo(new RemoteConnectorInfoImpl(_outerInstance, connectorInfo)); + } + + internal void AddAll(ICollection connectorInfos) where T1 : AbstractConnectorInfo + { + foreach (var connectorInfo in connectorInfos) + { + AddConnectorInfo(new RemoteConnectorInfoImpl(_outerInstance, connectorInfo)); + } + } + } + + private sealed class ControlMessageRequestFactory : + IRemoteRequestFactory + + { + public readonly List InfoLevels = new List(); + + public ControlMessageRequest CreateRemoteRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback) + { + return new ControlMessageRequest(context, requestId, completionCallback, InfoLevels); + } + } + + private class ControlMessageRequest : RemoteOperationRequest + { + private readonly List _infoLevels; + + public ControlMessageRequest(RemoteOperationContext context, long requestId, + Action + < + RemoteRequest + > completionCallback, + List infoLevels) + : base(context, requestId, completionCallback, CancellationToken.None) + { + _infoLevels = infoLevels; + } + + protected internal override RPCRequest CreateOperationRequest(RemoteOperationContext remoteContext) + { + ControlRequest builder = new ControlRequest(); + foreach (ControlRequest.Types.InfoLevel infoLevel in _infoLevels) + { + builder.InfoLevel.Add(infoLevel); + } + builder.LocalRequestId.Add(remoteContext.RemoteConnectionGroup.LocalRequests.Keys); + builder.RemoteRequestId.Add(remoteContext.RemoteConnectionGroup.RemoteRequests.Keys); + return new RPCRequest + { + ControlRequest = builder, + }; + } + + protected internal override bool HandleResponseMessage(WebSocketConnectionHolder sourceConnection, + Object message) + { + var controlResponse = message as ControlResponse; + if (controlResponse != null) + { + var response = MessagesUtil.DeserializeLegacy>(controlResponse.ConnectorInfos); + if (null != response && response.Any()) + { + foreach (var o in response) + { + var connectorInfo = o as AbstractConnectorInfo; + if (null != connectorInfo) + { + sourceConnection.RemoteConnectionContext.RemoteConnectionGroup._delegate + .AddOtherConnectorInfo(connectorInfo); + } + } + } + HandleResult(true); + } + else + { + return false; + } + return true; + } + } + } + + #endregion + + #region WebSocketConnectionHolder + + public abstract class WebSocketConnectionHolder : + IRemoteConnectionHolder + { +#if DEBUG + private static Int32 _counter; + private Int32 _id; + + public Int32 Id + { + get + { + if (_id == 0) + { + _id = Interlocked.Increment(ref _counter); + } + return _id; + } + } +#endif + + private readonly + ConcurrentDictionary + < + RemoteConnectionGroup. + AsyncMessageQueueRecord, Boolean> _messageQueue = + new ConcurrentDictionary + < + RemoteConnectionGroup + . + AsyncMessageQueueRecord, Boolean>(); + + private readonly AutoResetEvent _onMessageToSendEvent = new AutoResetEvent(true); + + /// + /// Adds a event handler to listen to the Disposed event on the WebSocketConnectionHolder. + /// + private event EventHandler DisposedEvent; + + public event EventHandler Disposed + { + add + { + // check if this is still running + if (Operational) + { + // add close listener + DisposedEvent += value; + // check the its state again + if (DisposedEvent != null && (!Operational && DisposedEvent.GetInvocationList().Contains(value))) + { + // if this was closed during the method call - notify the + // listener + try + { + value(this, EventArgs.Empty); + } + catch (Exception) + { + // ignored + } + } + } // if this is closed - notify the listener + else + { + try + { + value(this, EventArgs.Empty); + } + catch (Exception) + { + // ignored + } + } + } + remove { DisposedEvent -= value; } + } + + protected abstract Task WriteMessageAsync(byte[] entry, WebSocketMessageType messageType); + + protected async void WriteMessageAsync() + { + while (Operational) + { + do + { + foreach (var entry in _messageQueue) + { + if (await entry.Key.AcquireAndTryComplete(this)) + { + bool ignore; + _messageQueue.TryRemove(entry.Key, out ignore); +#if DEBUG + Debug.WriteLine("Dequeue from {2} Message:{1}, Pending:{0}", _messageQueue.Count, + entry.Key.Id, Id); +#endif + } + } + } while (_messageQueue.Any()); + + _onMessageToSendEvent.WaitOne(TimeSpan.FromMinutes(1)); + } +#if DEBUG + Debug.WriteLine("Finish Writting messages over Socket:{0}", Id); +#endif + foreach (var asyncQueueRecord in _messageQueue.Keys) + { + asyncQueueRecord.Detach(this); + } + } + + public virtual bool ReceiveHandshake(HandshakeMessage message) + { + if (null == RemoteConnectionContext) + { + Handshake(message); +#if DEBUG + Debug.WriteLine("New Connection accepted {0}:{1}", GetType().FullName, Id); +#endif + } + return HandHooked; + } + + public virtual bool HandHooked + { + get { return null != RemoteConnectionContext; } + } + + public void Dispose() + { + TryClose(); + _onMessageToSendEvent.Set(); + OnDisposed(); + } + + protected void OnDisposed() + { + try + { + var handler = DisposedEvent; + if (handler != null) handler(this, EventArgs.Empty); + } + catch (Exception e) + { +#if DEBUG + StringBuilder builder = new StringBuilder("DisposedEvent failed - "); + TraceUtil.ExceptionToString(builder, e, String.Empty); + Debug.WriteLine(builder.ToString()); +#endif + } + } + + protected abstract void Handshake(HandshakeMessage message); + + protected abstract void TryClose(); + + public abstract bool Operational { get; } + + public abstract RemoteOperationContext RemoteConnectionContext { get; } + + public void Enqueue( + RemoteConnectionGroup. + AsyncMessageQueueRecord record) + { + if (Operational && record.Accept(this)) + { + if (_messageQueue.TryAdd(record, true) && !Operational) + { + bool ignore; + _messageQueue.TryRemove(record, out ignore); + record.Detach(this); + } + else + { +#if DEBUG + Debug.WriteLine("Enqueue Socket:{0} Message:{1}", Id, record.Id); +#endif + _onMessageToSendEvent.Set(); + } + } + } + + + public class InternalAsyncMessageQueueRecord : + RemoteConnectionGroup. + AsyncMessageQueueRecord + { + private readonly WebSocketMessageType _messageType; + private readonly byte[] _message; + + public override string ToString() + { +#if DEBUG + return String.Format("{1} Message - Id:[{2}] size:{0}", _message.Length, _messageType, Id); +#else + return String.Format("{1} Message - size:{0}", _message.Length, _messageType); +#endif + } + + public InternalAsyncMessageQueueRecord(byte[] message) + { + _messageType = WebSocketMessageType.Binary; + _message = message; + } + + public InternalAsyncMessageQueueRecord(string message) + { + _messageType = WebSocketMessageType.Text; + _message = Encoding.UTF8.GetBytes(message); + } + + protected override Task DoSend(WebSocketConnectionHolder connection) + { +#if DEBUG + Debug.WriteLine("WebSocket: {0} writes {1} bytes of message: {2}", connection.Id, _message.Length, Id); +#endif + return connection.WriteMessageAsync(_message, _messageType); + } + } + + + public Task SendBytesAsync(byte[] data, CancellationToken cancellationToken) + { + var record = new InternalAsyncMessageQueueRecord(data); + Enqueue(record); + return record.SendAsync(TimeSpan.FromMinutes(1)); + } + + public Task SendStringAsync(string data, CancellationToken cancellationToken) + { + var record = new InternalAsyncMessageQueueRecord(data); + Enqueue(record); + return record.SendAsync(TimeSpan.FromMinutes(1)); + } + + public void SendPing(byte[] applicationData) + { + throw new NotImplementedException(); + } + + public void SendPong(byte[] applicationData) + { + throw new NotImplementedException(); + } + } + + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/packages.config b/dotnet/framework/FrameworkServer/packages.config new file mode 100755 index 00000000..097afb81 --- /dev/null +++ b/dotnet/framework/FrameworkServer/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkServer/version.template b/dotnet/framework/FrameworkServer/version.template new file mode 100755 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/FrameworkServer/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/FrameworkServerTests/FrameworkServerTests.csproj b/dotnet/framework/FrameworkServerTests/FrameworkServerTests.csproj new file mode 100755 index 00000000..2d97d9cf --- /dev/null +++ b/dotnet/framework/FrameworkServerTests/FrameworkServerTests.csproj @@ -0,0 +1,155 @@ + + + + + Debug + AnyCPU + {46365523-FA23-4AD4-9DB8-B0E195F00571} + Library + Properties + Org.ForgeRock.OpenICF.Framework.Remote + FrameworkServerTests + OpenICF Framework - Connector Server Tests + v4.5.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + UnitTest + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Google.ProtocolBuffers.3\lib\Google.Protobuf.dll + + + + + + False + ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + + + False + ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + + + False + ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + + + False + ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + + + + + + + + + + + + + {f140e8da-52b4-4159-992a-9da10ea8eefb} + Common + + + {8b24461b-456a-4032-89a1-cd418f7b5b62} + Framework + + + {5b011775-b121-4eee-a410-ba2d2f5bfb8b} + FrameworkInternal + + + {E6A207D2-E083-41BF-B522-D9D3EC09323E} + TestCommon + + + {b85c5a35-e3a2-4b04-9693-795e57d66de2} + FrameworkRpc + + + {5b47befd-c60b-4e80-943e-a7151ceea568} + FrameworkServer + + + {d1771e11-c7d3-43fd-9d87-46f1231846f1} + WcfServiceLibrary + + + {E5DCC07F-7B42-4AE9-8D6C-A15525476E0A} + ConnectorServerService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkServerTests/WcfServiceTests.cs b/dotnet/framework/FrameworkServerTests/WcfServiceTests.cs new file mode 100755 index 00000000..01b3a0f8 --- /dev/null +++ b/dotnet/framework/FrameworkServerTests/WcfServiceTests.cs @@ -0,0 +1,635 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Reactive.Linq; +using System.ServiceModel; +using System.ServiceModel.Channels; +using System.Threading; +using NUnit.Framework; +using Org.ForgeRock.OpenICF.Framework.ConnectorServerService; +using Org.ForgeRock.OpenICF.Framework.Service.WcfServiceLibrary; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Impl.Api.Remote; +using Org.IdentityConnectors.Test.Common; + +namespace Org.ForgeRock.OpenICF.Framework.Remote +{ + [TestFixture] + [Explicit] + public class ExplicitServerTest : ServerTestBase + { + protected override Action Start(ClientAuthenticationValidator validator, EndpointAddress serviceAddress) + { + return null; + } + + protected override int FreePort + { + get { return 8759; } + } + } + + [TestFixture] + public class NonWcfServerTest : ServerTestBase + { + protected override Action Start(ClientAuthenticationValidator validator, EndpointAddress serviceAddress) + { + VtortConnectorServiceHost host = new VtortConnectorServiceHost(validator, serviceAddress.Uri); + host.Open(); + + return () => host.Close(); + } + } + + + [TestFixture] + public class WcfServerTest : ServerTestBase + { + protected override Action Start(ClientAuthenticationValidator validator, EndpointAddress serviceAddress) + { + ServiceHost host = new ConnectorServiceHost(validator, serviceAddress.Uri); + + CustomBinding binding = new CustomBinding(); + binding.Elements.Add(new ByteStreamMessageEncodingBindingElement()); + + HttpTransportBindingElement transport = new HttpTransportBindingElement + { + WebSocketSettings = + { + TransportUsage = WebSocketTransportUsage.Always, + CreateNotificationOnConnection = true, + SubProtocol = "v1.openicf.forgerock.org" + }, + Realm = "openicf", + AuthenticationScheme = AuthenticationSchemes.Basic, + }; + + binding.Elements.Add(transport); + host.AddServiceEndpoint(typeof (IWebSocketService), binding, ""); + + host.Open(); + + return () => { host.Close(); }; + } + } + + + [TestFixture] + public abstract class ServerTestBase + { + protected static readonly ConnectorKey TestConnectorKey = new ConnectorKey("TestBundleV1.Connector", "1.0.0.0", + "org.identityconnectors.testconnector.TstConnector"); + + protected static readonly ConnectorKey TestStatefulConnectorKey = new ConnectorKey("TestBundleV1.Connector", + "1.0.0.0", + "org.identityconnectors.testconnector.TstStatefulConnector"); + + protected static readonly ConnectorKey TestPoolableStatefulConnectorKey = + new ConnectorKey("TestBundleV1.Connector", "1.0.0.0", + "org.identityconnectors.testconnector.TstStatefulPoolableConnector"); + + private Action _close; + private ConnectorFramework _clientConnectorFramework; + private RemoteWSFrameworkConnectionInfo _connectionInfo; + + + protected virtual int FreePort + { + get { return 8000; } + } + + private static int FreeTcpPort() + { + //netsh http add urlacl url=http://+:8000/openicf user=DOMAIN\user + TcpListener l = new TcpListener(IPAddress.Loopback, 0); + l.Start(); + int port = ((IPEndPoint) l.LocalEndpoint).Port; + l.Stop(); + return port; + } + + protected abstract Action Start(ClientAuthenticationValidator validator, EndpointAddress serviceAddress); + + [TestFixtureSetUp] + public void Init() + { + try + { + ConnectorFramework serverConnectorFramework = new ConnectorFramework(); + ConnectorServerService.ConnectorServerService.InitializeConnectors(serverConnectorFramework.LocalManager); + + foreach (var connectorInfo in serverConnectorFramework.LocalManager.ConnectorInfos) + { + Trace.TraceInformation("Found Connector {0}", connectorInfo.ConnectorKey); + } + + int freePort = FreePort; + EndpointAddress serviceAddress = + new EndpointAddress(String.Format("http://localhost:{0}/openicf", freePort)); + + var secureString = new GuardedString(); + "changeit".ToCharArray().ToList().ForEach(p => secureString.AppendChar(p)); + + ClientAuthenticationValidator validator = new ClientAuthenticationValidator(); + validator.Add(new SingleTenantPrincipal(serverConnectorFramework), secureString.GetBase64SHA1Hash()); + + _close = Start(validator, serviceAddress); + _close += () => serverConnectorFramework.Dispose(); + + // ---------- + + _clientConnectorFramework = new ConnectorFramework(); + + _connectionInfo = new RemoteWSFrameworkConnectionInfo + { + RemoteUri = new Uri(String.Format("http://localhost.fiddler:{0}/openicf", freePort)), + Principal = ConnectionPrincipal.DefaultName, + Password = secureString + }; + } + catch (Exception e) + { + TraceUtil.TraceException("Failed", e); + throw; + } + } + + [TestFixtureTearDown] + public void Dispose() + { + if (null != _close) + { + _close(); + } + } + + [SetUp] + public void InitTest() + { + /* ... */ + } + + [TearDown] + public void DisposeTest() + { + /* ... */ + } + + public ConnectorFramework ConnectorFramework + { + get { return _clientConnectorFramework; } + } + + protected AsyncRemoteConnectorInfoManager ConnectorInfoManager + { + get { return ConnectorFramework.GetRemoteManager(_connectionInfo); } + } + + protected ConnectorFacade ConnectorFacade + { + get { return GetConnectorFacade(false, false); } + } + + protected ConnectorFacade GetConnectorFacade(Boolean caseIgnore, + Boolean returnNullTest) + { + return ConnectorInfoManager.FindConnectorInfoAsync(TestStatefulConnectorKey) + .ContinueWith(task => + { + if (task.IsCompleted) + { + var info = task.Result; + APIConfiguration api = info.CreateDefaultAPIConfiguration(); + ConfigurationProperties props = api.ConfigurationProperties; + + props.GetProperty("randomString").Value = StringUtil.RandomString(); + props.GetProperty("caseIgnore").Value = caseIgnore; + props.GetProperty("returnNullTest").Value = returnNullTest; + props.GetProperty("failValidation").Value = false; + props.GetProperty("testObjectClass").Value = + new[] {ObjectClass.ACCOUNT_NAME, ObjectClass.GROUP_NAME}; + api.ProducerBufferSize = 0; + return ConnectorFramework.NewInstance(api); + } + task.Wait(); + return null; + }).Result; + } + + + [Test] + public void TestRequiredConnectorInfo() + { + IAsyncConnectorInfoManager manager = ConnectorInfoManager; + Assert.IsNotNull(manager); + + var result = manager.FindConnectorInfoAsync(TestConnectorKey); + Assert.IsTrue(result.Wait(TimeSpan.FromMinutes(5))); + Assert.IsNotNull(result.Result); + + result = manager.FindConnectorInfoAsync(TestStatefulConnectorKey); + Assert.IsTrue(result.Wait(TimeSpan.FromMinutes(5))); + Assert.IsNotNull(result.Result); + + result = manager.FindConnectorInfoAsync(TestPoolableStatefulConnectorKey); + Assert.IsTrue(result.Wait(TimeSpan.FromMinutes(5))); + Assert.IsNotNull(result.Result); + } + + [Test] + public void TestValidate() + { + IAsyncConnectorInfoManager manager = ConnectorInfoManager; + var task = manager.FindConnectorInfoAsync(TestStatefulConnectorKey); + Assert.IsTrue(task.Wait(TimeSpan.FromMinutes(5))); + + ConnectorInfo info = task.Result; + Assert.IsNotNull(info); + APIConfiguration api = info.CreateDefaultAPIConfiguration(); + + ConfigurationProperties props = api.ConfigurationProperties; + ConfigurationProperty property = props.GetProperty("failValidation"); + property.Value = false; + + ConnectorFacade facade = ConnectorFramework.NewInstance(api); + facade.Validate(); + property.Value = true; + facade = ConnectorFramework.NewInstance(api); + try + { + Thread.CurrentThread.CurrentUICulture = new CultureInfo("en"); + facade.Validate(); + Assert.Fail("exception expected"); + } + catch (Exception e) + { + TraceUtil.TraceException("Test Exception", e); + Assert.AreEqual("validation failed en", e.Message); + } + try + { + Thread.CurrentThread.CurrentUICulture = new CultureInfo("es"); + facade.Validate(); + Assert.Fail("exception expected"); + } + catch (RemoteWrappedException e) + { + Assert.AreEqual("validation failed es", e.Message); + } + + // call test and also test that locale is propagated + // properly + try + { + Thread.CurrentThread.CurrentUICulture = new CultureInfo("en"); + facade.Test(); + Assert.Fail("exception expected"); + } + catch (Exception e) + { + TraceUtil.TraceException("Test Exception", e); + Assert.AreEqual("test failed en", e.Message); + } + } + + [Test] + public virtual void TestConfigurationUpdate() + { + IAsyncConnectorInfoManager manager = ConnectorInfoManager; + var taskA = manager.FindConnectorInfoAsync(TestStatefulConnectorKey); + var taskB = manager.FindConnectorInfoAsync(TestPoolableStatefulConnectorKey); + Assert.IsTrue(taskA.Wait(TimeSpan.FromMinutes(5))); + Assert.IsTrue(taskB.Wait(TimeSpan.FromMinutes(5))); + + ConnectorInfo[] infos = + { + taskA.Result, + taskB.Result + }; + foreach (ConnectorInfo info in infos) + { + APIConfiguration api = info.CreateDefaultAPIConfiguration(); + + ConfigurationProperties props = api.ConfigurationProperties; + props.GetProperty("randomString").Value = StringUtil.RandomString(); + api.ProducerBufferSize = 0; + + var listener = new ConfigurationPropertyChangeListener(); + api.ChangeListener = listener; + + ConnectorFacade facade = ConnectorFramework.NewInstance(api); + + ScriptContextBuilder builder = new ScriptContextBuilder + { + ScriptLanguage = "Boo", + ScriptText = "connector.Update()" + }; + + facade.RunScriptOnConnector(builder.Build(), null); + + for (int i = 0; (i < 25 && null == listener.Changes); i++) + { + Thread.Sleep(1000); + } + Assert.NotNull(listener.Changes); + Assert.AreEqual(listener.Changes.Count, 1); + Assert.AreEqual(listener.Changes.First().Value, "change"); + } + } + + internal class ConfigurationPropertyChangeListener : IConfigurationPropertyChangeListener + { + internal IList Changes { get; set; } + + public virtual void ConfigurationPropertyChange(IList changes) + { + Changes = changes; + } + } + + [Test] + public void TestNullOperations() + { + IAsyncConnectorInfoManager manager = ConnectorInfoManager; + var task = manager.FindConnectorInfoAsync(TestStatefulConnectorKey); + Assert.IsTrue(task.Wait(TimeSpan.FromMinutes(5))); + + ConnectorFacade facade = GetConnectorFacade(true, true); + OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(); + facade.Test(); + Assert.IsNull(facade.Schema()); + + var guardedString = new GuardedString(); + "Passw0rd".ToCharArray().ToList().ForEach(p => guardedString.AppendChar(p)); + + Uid uid = facade.Create(ObjectClass.ACCOUNT, + CollectionUtil.NewSet(new Name("CREATE_01"), ConnectorAttributeBuilder.BuildPassword(guardedString)), + optionsBuilder.Build()); + Assert.IsNull(uid); + + Uid resolvedUid = facade.ResolveUsername(ObjectClass.ACCOUNT, "CREATE_01", optionsBuilder.Build()); + Assert.IsNull(resolvedUid); + + + Uid authenticatedUid = facade.Authenticate(ObjectClass.ACCOUNT, "CREATE_01", guardedString, + optionsBuilder.Build()); + Assert.IsNull(authenticatedUid); + + SyncToken token = facade.GetLatestSyncToken(ObjectClass.ACCOUNT); + Assert.IsNull(token); + + SyncToken lastToken = facade.Sync(ObjectClass.ACCOUNT, new SyncToken(-1), + new SyncResultsHandler {Handle = delta => true}, optionsBuilder.Build()); + + Assert.IsNull(lastToken); + + SearchResult searchResult = facade.Search(ObjectClass.ACCOUNT, null, + new ResultsHandler {Handle = connectorObject => true}, optionsBuilder.Build()); + + Assert.IsNull(searchResult); + + Uid updatedUid = facade.Update(ObjectClass.ACCOUNT, new Uid("1"), + CollectionUtil.NewSet(ConnectorAttributeBuilder.BuildLockOut(true)), optionsBuilder.Build()); + Assert.IsNull(updatedUid); + + ConnectorObject co = facade.GetObject(ObjectClass.ACCOUNT, new Uid("1"), optionsBuilder.Build()); + Assert.IsNull(co); + + + ScriptContextBuilder contextBuilder = new ScriptContextBuilder + { + ScriptLanguage = "Boo", + ScriptText = "arg" + }; + contextBuilder.AddScriptArgument("arg", "test"); + + object o = facade.RunScriptOnConnector(contextBuilder.Build(), optionsBuilder.Build()); + Assert.AreEqual(o, "test"); + o = facade.RunScriptOnResource(contextBuilder.Build(), optionsBuilder.Build()); + Assert.IsNull(o); + } + + [Test] + public void TestOperations() + { + IAsyncConnectorInfoManager manager = ConnectorInfoManager; + var task = manager.FindConnectorInfoAsync(TestStatefulConnectorKey); + Assert.IsTrue(task.Wait(TimeSpan.FromMinutes(5))); + + ConnectorFacade facade = ConnectorFacade; + facade.Test(); + Assert.IsNotNull(facade.Schema()); + + var guardedString = new GuardedString(); + "Passw0rd".ToCharArray().ToList().ForEach(p => guardedString.AppendChar(p)); + + Uid uid1 = facade.Create(ObjectClass.ACCOUNT, + CollectionUtil.NewSet(new Name("CREATE_01"), ConnectorAttributeBuilder.BuildPassword(guardedString)), + null); + Assert.IsNotNull(uid1); + + Uid uid2 = facade.Create(ObjectClass.ACCOUNT, + CollectionUtil.NewSet(new Name("CREATE_02"), ConnectorAttributeBuilder.BuildPassword(guardedString)), + null); + + Assert.AreNotEqual(uid1, uid2); + + Uid resolvedUid = facade.ResolveUsername(ObjectClass.ACCOUNT, "CREATE_01", null); + Assert.AreEqual(uid1, resolvedUid); + + Uid authenticatedUid = facade.Authenticate(ObjectClass.ACCOUNT, "CREATE_01", guardedString, null); + Assert.AreEqual(uid1, authenticatedUid); + + try + { + guardedString = new GuardedString(); + "wrongPassw0rd".ToCharArray().ToList().ForEach(p => guardedString.AppendChar(p)); + facade.Authenticate(ObjectClass.ACCOUNT, "CREATE_01", guardedString, null); + Assert.Fail("This should fail"); + } + catch (Exception e) + { + Assert.AreEqual("Invalid Password", e.Message); + } + + SyncToken token = facade.GetLatestSyncToken(ObjectClass.ACCOUNT); + Assert.AreEqual(token.Value, 2); + + IList changes = new List(); + Int32? index = null; + + SyncToken lastToken = facade.Sync(ObjectClass.ACCOUNT, new SyncToken(-1), new SyncResultsHandler + { + Handle = delta => + { + Int32? previous = index; + index = (Int32?) delta.Token.Value; + if (null != previous) + { + Assert.IsTrue(previous < index); + } + changes.Add(delta); + return true; + } + }, null); + + Assert.AreEqual(changes.Count, 2); + Assert.AreEqual(facade.GetObject(ObjectClass.ACCOUNT, uid1, null).Uid, uid1); + Assert.AreEqual(token, lastToken); + + IList connectorObjects = new List(); + facade.Search(ObjectClass.ACCOUNT, + FilterBuilder.Or(FilterBuilder.EqualTo(new Name("CREATE_02")), + FilterBuilder.StartsWith(new Name("CREATE"))), new ResultsHandler + { + Handle = + connectorObject => + { + connectorObjects.Add(connectorObject); + return true; + } + }, null); + Assert.AreEqual(connectorObjects.Count, 2); + + connectorObjects = new List(); + facade.Search(ObjectClass.ACCOUNT, null, new ResultsHandler + { + Handle = + connectorObject => + { + connectorObjects.Add(connectorObject); + return true; + } + }, null); + Assert.AreEqual(connectorObjects.Count, 2); + + Uid updatedUid = facade.Update(ObjectClass.ACCOUNT, uid1, + CollectionUtil.NewSet(ConnectorAttributeBuilder.BuildLockOut(true)), null); + ConnectorObject co = facade.GetObject(ObjectClass.ACCOUNT, updatedUid, null); + var isLockedOut = ConnectorAttributeUtil.IsLockedOut(co); + Assert.IsTrue(isLockedOut != null && (bool) isLockedOut); + + facade.Delete(ObjectClass.ACCOUNT, updatedUid, null); + Assert.IsNull(facade.GetObject(ObjectClass.ACCOUNT, updatedUid, null)); + } + + [Test] + public void TestScriptOperations() + { + IAsyncConnectorInfoManager manager = ConnectorInfoManager; + var task = manager.FindConnectorInfoAsync(TestStatefulConnectorKey); + Assert.IsTrue(task.Wait(TimeSpan.FromMinutes(5))); + + ConnectorFacade facade = ConnectorFacade; + + ScriptContextBuilder contextBuilder = new ScriptContextBuilder + { + ScriptLanguage = "Boo", + ScriptText = "arg", + }; + contextBuilder.AddScriptArgument("arg", "test"); + + object o = facade.RunScriptOnConnector(contextBuilder.Build(), null); + Assert.AreEqual(o, "test"); + o = facade.RunScriptOnResource(contextBuilder.Build(), null); + Assert.AreEqual(o, "test"); + } + + [Test] + public void TestSubscriptionOperation() + { + IAsyncConnectorInfoManager manager = ConnectorInfoManager; + var task = manager.FindConnectorInfoAsync(TestStatefulConnectorKey); + Assert.IsTrue(task.Wait(TimeSpan.FromMinutes(5))); + + ConnectorFacade facade = ConnectorFacade; + ToListResultsHandler handler = new ToListResultsHandler(); + CountdownEvent cde = new CountdownEvent(1); + var localFacade = facade; + var connectorObjectObservable = + Observable.Create(o => localFacade.Subscribe(ObjectClass.ACCOUNT, null, o, null)); + + + var subscription = connectorObjectObservable.Subscribe( + co => + { + Console.WriteLine(@"Connector Event received:{0}", co.Uid.GetUidValue()); + handler.ResultsHandler.Handle(co); + }, + ex => + { + cde.Signal(); + Assert.AreEqual(handler.Objects.Count, 10, "Uncompleted subscription"); + }); + + + cde.Wait(new TimeSpan(0, 0, 25)); + subscription.Dispose(); + Assert.AreEqual(10, handler.Objects.Count); + + handler = new ToListResultsHandler(); + cde = new CountdownEvent(1); + + var syncDeltaObservable = + Observable.Create(o => localFacade.Subscribe(ObjectClass.ACCOUNT, null, o, null)); + + IDisposable[] subscriptions = new IDisposable[1]; + subscriptions[0] = syncDeltaObservable.Subscribe( + delta => + { + Console.WriteLine(@"Sync Event received:{0}", delta.Token.Value); + if (((int?) delta.Token.Value) > 2) + { + subscriptions[0].Dispose(); + cde.Signal(); + } + handler.ResultsHandler.Handle(delta.Object); + }, + ex => + { + cde.Signal(); + Assert.Fail("Failed Subscription {0}", ex); + }); + + cde.Wait(new TimeSpan(0, 0, 25)); + for (int i = 0; i < 5 && !(handler.Objects.Count > 2); i++) + { + Console.WriteLine(@"Wait for result handler thread to complete: {0}", i); + Thread.Sleep(200); // Wait to complete all other threads + } + Assert.IsTrue(handler.Objects.Count < 10 && handler.Objects.Count > 2); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkServerTests/packages.config b/dotnet/framework/FrameworkServerTests/packages.config new file mode 100755 index 00000000..4b8fa985 --- /dev/null +++ b/dotnet/framework/FrameworkServerTests/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkServerTests/version.template b/dotnet/framework/FrameworkServerTests/version.template new file mode 100755 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/FrameworkServerTests/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/CollectionUtilTests.cs b/dotnet/framework/FrameworkTests/CollectionUtilTests.cs new file mode 100644 index 00000000..beb0fc5f --- /dev/null +++ b/dotnet/framework/FrameworkTests/CollectionUtilTests.cs @@ -0,0 +1,161 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Org.IdentityConnectors.Common; +namespace FrameworkTests +{ + [TestFixture] + public class CollectionUtilTests + { + [Test] + public void TestEquals() + { + Assert.IsTrue(CollectionUtil.Equals(null, null)); + Assert.IsFalse(CollectionUtil.Equals(null, "str")); + Assert.IsTrue(CollectionUtil.Equals("str", "str")); + + byte[] arr1 = new byte[] { 1, 2, 3 }; + byte[] arr2 = new byte[] { 1, 2, 3 }; + byte[] arr3 = new byte[] { 1, 2, 4 }; + byte[] arr4 = new byte[] { 1, 2 }; + int[] arr5 = new int[] { 1, 2, 3 }; + + Assert.IsTrue(CollectionUtil.Equals(arr1, arr2)); + Assert.IsFalse(CollectionUtil.Equals(arr2, arr3)); + Assert.IsFalse(CollectionUtil.Equals(arr2, arr4)); + Assert.IsFalse(CollectionUtil.Equals(arr2, arr5)); + + IList list1 = new List(); + IList list2 = new List(); + list1.Add(arr1); + list2.Add(arr2); + + Assert.IsTrue(CollectionUtil.Equals(list1, list2)); + + list2.Add(arr2); + Assert.IsFalse(CollectionUtil.Equals(list1, list2)); + + list1.Add(arr1); + Assert.IsTrue(CollectionUtil.Equals(list1, list2)); + + list1.Add(arr1); + list2.Add(arr3); + Assert.IsFalse(CollectionUtil.Equals(list1, list2)); + + IDictionary map1 = new Dictionary(); + IDictionary map2 = new Dictionary(); + map1["key1"] = arr1; + map2["key1"] = arr2; + Assert.IsTrue(CollectionUtil.Equals(map1, map2)); + map2["key2"] = arr2; + Assert.IsFalse(CollectionUtil.Equals(map1, map2)); + map1["key2"] = arr1; + Assert.IsTrue(CollectionUtil.Equals(map1, map2)); + map1["key2"] = arr3; + Assert.IsFalse(CollectionUtil.Equals(map1, map2)); + + ICollection set1 = new HashSet(); + ICollection set2 = new HashSet(); + set1.Add("val"); + set2.Add("val"); + Assert.IsTrue(CollectionUtil.Equals(set1, set2)); + set2.Add("val2"); + Assert.IsFalse(CollectionUtil.Equals(set1, set2)); + set1.Add("val2"); + Assert.IsTrue(CollectionUtil.Equals(set1, set2)); + } + [Test] + public void TestHashCode() + { + Assert.AreEqual(0, CollectionUtil.GetHashCode(null)); + Assert.AreEqual("str".GetHashCode(), CollectionUtil.GetHashCode("str")); + + byte[] arr1 = new byte[] { 1, 2, 3 }; + byte[] arr2 = new byte[] { 1, 2, 3 }; + byte[] arr3 = new byte[] { 1, 2, 4 }; + byte[] arr4 = new byte[] { 1, 2 }; + int[] arr5 = new int[] { 1, 2, 3 }; + + Assert.AreEqual(CollectionUtil.GetHashCode(arr1), + CollectionUtil.GetHashCode(arr2)); + Assert.IsFalse(CollectionUtil.GetHashCode(arr2) == + CollectionUtil.GetHashCode(arr3)); + Assert.IsFalse(CollectionUtil.GetHashCode(arr2) == + CollectionUtil.GetHashCode(arr4)); + Assert.IsTrue(CollectionUtil.GetHashCode(arr2) == + CollectionUtil.GetHashCode(arr5)); + + List list1 = new List(); + List list2 = new List(); + list1.Add(arr1); + list2.Add(arr2); + + Assert.IsTrue(CollectionUtil.GetHashCode(list1) == + CollectionUtil.GetHashCode(list2)); + + list2.Add(arr2); + Assert.IsFalse(CollectionUtil.GetHashCode(list1) == + CollectionUtil.GetHashCode(list2)); + + list1.Add(arr1); + Assert.IsTrue(CollectionUtil.GetHashCode(list1) == + CollectionUtil.GetHashCode(list2)); + + list1.Add(arr1); + list2.Add(arr3); + Assert.IsFalse(CollectionUtil.GetHashCode(list1) == + CollectionUtil.GetHashCode(list2)); + + Dictionary map1 = new Dictionary(); + Dictionary map2 = new Dictionary(); + map1["key1"] = arr1; + map2["key1"] = arr2; + Assert.IsTrue(CollectionUtil.GetHashCode(map1) == + CollectionUtil.GetHashCode(map2)); + map2["key2"] = arr2; + Assert.IsFalse(CollectionUtil.GetHashCode(map1) == + CollectionUtil.GetHashCode(map2)); + map1["key2"] = arr1; + Assert.IsTrue(CollectionUtil.GetHashCode(map1) == + CollectionUtil.GetHashCode(map2)); + map1["key2"] = arr3; + Assert.IsFalse(CollectionUtil.GetHashCode(map1) == + CollectionUtil.GetHashCode(map2)); + + HashSet set1 = new HashSet(); + HashSet set2 = new HashSet(); + set1.Add("val"); + set2.Add("val"); + Assert.IsTrue(CollectionUtil.GetHashCode(set1) == + CollectionUtil.GetHashCode(set2)); + set2.Add("val2"); + Assert.IsFalse(CollectionUtil.GetHashCode(set1) == + CollectionUtil.GetHashCode(set2)); + set1.Add("val2"); + Assert.IsTrue(CollectionUtil.GetHashCode(set1) == + CollectionUtil.GetHashCode(set2)); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/ConnectorAttributeUtilTests.cs b/dotnet/framework/FrameworkTests/ConnectorAttributeUtilTests.cs new file mode 100644 index 00000000..e063be28 --- /dev/null +++ b/dotnet/framework/FrameworkTests/ConnectorAttributeUtilTests.cs @@ -0,0 +1,371 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security; +using NUnit.Framework; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Objects; + +namespace FrameworkTests +{ + #region ConnectorAttributesAccessorTest + [TestFixture] + public class ConnectorAttributesAccessorTest + { + [Test] + public virtual void TestGetUid() + { + Assert.AreEqual(Testable.GetUid(), new Uid("UID001")); + } + + [Test] + public virtual void TestGetName() + { + Assert.AreEqual(Testable.GetName(), new Name("NAME001")); + } + + [Test] + public virtual void TestGetEnabled() + { + Assert.IsFalse(Testable.GetEnabled(true)); + } + + [Test] + public virtual void testGetPassword() + { + SecureString secret = new SecureString(); + "Passw0rd".ToCharArray().ToList().ForEach(secret.AppendChar); + Assert.AreEqual(new GuardedString(secret), Testable.GetPassword()); + } + + [Test] + public virtual void TestFindList() + { + Assert.AreEqual(Testable.FindList("attributeFloatMultivalue"), CollectionUtil.NewList(new float?[3] { 24F, 25F, null })); + } + + [Test] + public virtual void TestListAttributeNames() + { + Assert.IsTrue(Testable.ListAttributeNames().Contains("attributeboolean")); + } + + [Test] + public virtual void TestHasAttribute() + { + Assert.IsTrue(Testable.HasAttribute("attributeboolean")); + } + + [Test] + public virtual void TestFindString() + { + Assert.AreEqual(Testable.FindString("attributeString"), "retipipiter"); + } + + [Test] + public virtual void TestFindStringList() + { + Assert.AreEqual(Testable.FindStringList("attributeStringMultivalue"), CollectionUtil.NewList(new String[2] { "value1", "value2" })); + } + + [Test] + public virtual void TestFindCharacter() + { + Assert.AreEqual((char)Testable.FindCharacter("attributechar"), 'a'); + Assert.AreEqual((char)Testable.FindCharacter("attributecharacter"), 'd'); + } + + [Test] + public virtual void TestFindInteger() + { + Assert.AreEqual((int)Testable.FindInteger("attributeint"), 26); + Assert.AreEqual((int)Testable.FindInteger("attributeinteger"), 29); + } + + [Test] + public virtual void TestFindLong() + { + Assert.AreEqual((long)Testable.FindLong("attributelongp"), 11L); + Assert.AreEqual((long)Testable.FindLong("attributelong"), 14L); + } + + [Test] + public virtual void TestFindDouble() + { + Assert.AreEqual(Testable.FindDouble("attributedoublep"), double.MinValue); + Assert.AreEqual(Testable.FindDouble("attributedouble"), 17D); + } + + [Test] + public virtual void TestFindFloat() + { + Assert.AreEqual(Testable.FindFloat("attributefloatp"), 20F); + Assert.AreEqual(Testable.FindFloat("attributefloat"), 23F); + } + + [Test] + public virtual void TestFindBoolean() + { + var findBoolean = Testable.FindBoolean("attributebooleanp"); + Assert.IsTrue(findBoolean != null && (bool)findBoolean); + var boolean = Testable.FindBoolean("attributeboolean"); + Assert.IsFalse(boolean != null && (bool)boolean); + } + + [Test] + public virtual void TestFindByte() + { + Assert.AreEqual((byte)Testable.FindByte("attributebytep"), (byte)48); + Assert.AreEqual((byte)Testable.FindByte("attributebyte"), (byte)51); + } + + [Test] + public virtual void TestFindByteArray() + { + Assert.AreEqual(Testable.FindByteArray("attributeByteArray"), System.Text.Encoding.UTF8.GetBytes("array")); + } + + [Test] + public virtual void TestFindBigDecimal() + { + Assert.AreEqual(Testable.FindBigDecimal("attributeBigDecimal"), new BigDecimal(new BigInteger("1"), 0)); + } + + [Test] + public virtual void FestFindBigInteger() + { + Assert.AreEqual(Testable.FindBigInteger("attributeBigInteger"), new BigInteger("1")); + } + + [Test] + public virtual void TestFindGuardedByteArray() + { + var expected = new GuardedByteArray(); + System.Text.Encoding.UTF8.GetBytes("array").ToList().ForEach(expected.AppendByte); + Assert.AreEqual(expected, Testable.FindGuardedByteArray("attributeGuardedByteArray")); + } + + [Test] + public virtual void TestFindGuardedString() + { + SecureString secret = new SecureString(); + "secret".ToCharArray().ToList().ForEach(secret.AppendChar); + Assert.AreEqual(new GuardedString(secret), Testable.FindGuardedString("attributeGuardedString")); + } + + [Test] + public void TestFindDictionary() + { + Assert.AreEqual(SampleMap,Testable.FindDictionary("attributeMap")); + } + + + private ConnectorAttributesAccessor Testable + { + get + { + ICollection attributes = new HashSet(); + attributes.Add(new Uid("UID001")); + attributes.Add(new Name("NAME001")); + attributes.Add(ConnectorAttributeBuilder.BuildEnabled(false)); + SecureString password = new SecureString(); + "Passw0rd".ToCharArray().ToList().ForEach(p => password.AppendChar(p)); + attributes.Add(ConnectorAttributeBuilder.BuildPassword(password)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeString", "retipipiter")); + attributes.Add(ConnectorAttributeBuilder.Build("attributeStringMultivalue", "value1", "value2")); + + attributes.Add(ConnectorAttributeBuilder.Build("attributelongp", 11L)); + attributes.Add(ConnectorAttributeBuilder.Build("attributelongpMultivalue", 12L, 13L)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeLong", new long?(14L))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeLongMultivalue", Convert.ToInt64(15), Convert.ToInt64(16), null)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributechar", 'a')); + attributes.Add(ConnectorAttributeBuilder.Build("attributecharMultivalue", 'b', 'c')); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeCharacter", new char?('d'))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeCharacterMultivalue", new char?('e'), new char?('f'), null)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributedoublep", double.MinValue)); + attributes.Add(ConnectorAttributeBuilder.Build("attributedoublepMultivalue", double.Epsilon, double.MaxValue)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeDouble", new double?(17D))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeDoubleMultivalue", new double?(18D), new double?(19D), null)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributefloatp", 20F)); + attributes.Add(ConnectorAttributeBuilder.Build("attributefloatpMultivalue", 21F, 22F)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeFloat", new float?(23F))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeFloatMultivalue", new float?(24F), new float?(25F), null)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeint", 26)); + attributes.Add(ConnectorAttributeBuilder.Build("attributeintMultivalue", 27, 28)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeInteger", new int?(29))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeIntegerMultivalue", new int?(30), new int?(31), null)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributebooleanp", true)); + attributes.Add(ConnectorAttributeBuilder.Build("attributebooleanpMultivalue", true, false)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeBoolean", Convert.ToBoolean(false))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeBooleanMultivalue", Convert.ToBoolean(true), Convert.ToBoolean(false), null)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributebytep", (byte)48)); + attributes.Add(ConnectorAttributeBuilder.Build("attributebytepMultivalue", (byte)49, (byte)50)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeByte", new byte?((byte)51))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeByteMultivalue", new byte?((byte)52), new byte?((byte)53), null)); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeByteArray", System.Text.Encoding.UTF8.GetBytes("array"))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeByteArrayMultivalue", System.Text.Encoding.UTF8.GetBytes("item1"), System.Text.Encoding.UTF8.GetBytes("item2"))); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeBigDecimal", new BigDecimal(new BigInteger("1"), 0))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeBigDecimalMultivalue", new BigDecimal(new BigInteger("0"), 0), new BigDecimal(new BigInteger("10"), 0))); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeBigInteger", new BigInteger("1"))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeBigIntegerMultivalue", new BigInteger("0"), new BigInteger("10"))); + + GuardedByteArray array = new GuardedByteArray(); + GuardedByteArray item1 = new GuardedByteArray(); + GuardedByteArray item2 = new GuardedByteArray(); + System.Text.Encoding.UTF8.GetBytes("array").ToList().ForEach(p => array.AppendByte(p)); + System.Text.Encoding.UTF8.GetBytes("item1").ToList().ForEach(p => item1.AppendByte(p)); + System.Text.Encoding.UTF8.GetBytes("item2").ToList().ForEach(p => item2.AppendByte(p)); + attributes.Add(ConnectorAttributeBuilder.Build("attributeGuardedByteArray", array)); + attributes.Add(ConnectorAttributeBuilder.Build("attributeGuardedByteArrayMultivalue", item1, item2)); + + SecureString secret = new SecureString(); + SecureString secret1 = new SecureString(); + SecureString secret2 = new SecureString(); + "secret".ToCharArray().ToList().ForEach(p => secret.AppendChar(p)); + "secret1".ToCharArray().ToList().ForEach(p => secret1.AppendChar(p)); + "secret2".ToCharArray().ToList().ForEach(p => secret2.AppendChar(p)); + attributes.Add(ConnectorAttributeBuilder.Build("attributeGuardedString", new GuardedString(secret))); + attributes.Add(ConnectorAttributeBuilder.Build("attributeGuardedStringMultivalue", new GuardedString(secret1), new GuardedString(secret2))); + + attributes.Add(ConnectorAttributeBuilder.Build("attributeMap", SampleMap)); + attributes.Add(ConnectorAttributeBuilder.Build("attributeMapMultivalue", SampleMap, SampleMap)); + return new ConnectorAttributesAccessor(attributes); + } + } + + private IDictionary SampleMap + { + get + { + IDictionary ret = CollectionUtil.NewDictionary("string", "String", "number", 43, "trueOrFalse", true, "nullValue", null, "collection", CollectionUtil.NewList(new string[2] { "item1", "item2" })); + ret["object"] = CollectionUtil.NewDictionary("key1", "value1", "key2", "value2"); + return ret; + } + } + } + #endregion + #region ConnectorAttributeUtilTests + [TestFixture] + public class ConnectorAttributeUtilTests + { + + [Test] + public void TestNamesEqual() + { + Assert.IsTrue(ConnectorAttributeUtil.NamesEqual("givenName", "givenname")); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TestGetSingleValue() + { + object TEST_VALUE = 1L; + ConnectorAttribute attr = ConnectorAttributeBuilder.Build("long", TEST_VALUE); + object value = ConnectorAttributeUtil.GetSingleValue(attr); + Assert.AreEqual(TEST_VALUE, value); + + // test null + attr = ConnectorAttributeBuilder.Build("long"); + value = ConnectorAttributeUtil.GetSingleValue(attr); + Assert.IsNull(value); + // test empty + attr = ConnectorAttributeBuilder.Build("long", new List()); + value = ConnectorAttributeUtil.GetSingleValue(attr); + Assert.IsNull(value); + // test illegal argument exception + ConnectorAttributeUtil.GetSingleValue(ConnectorAttributeBuilder.Build("bob", 1, 2, 3)); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TestDictionaryIntegerAttribute() + { + Dictionary map = new Dictionary(); + map[1] = "NOK"; + + ConnectorAttributeBuilder bld = new ConnectorAttributeBuilder(); + bld.AddValue(map); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TestDictionaryShortAttribute() + { + Dictionary map1 = new Dictionary(); + map1["string"] = "NOK"; + + Dictionary map2 = new Dictionary(); + map2["map1"] = map1; + map2["list"] = new List { 1, 2, 3, (short)5 }; + + Dictionary map3 = new Dictionary(); + map3["map2"] = map2; + + Dictionary map4 = new Dictionary(); + map4["map3"] = map3; + + ConnectorAttributeBuilder.Build("map", map4); + } + + [Test] + public void TestDictionaryAttribute() + { + Dictionary map1 = new Dictionary(); + map1["string"] = "OK"; + + Dictionary map2 = new Dictionary(); + map2["map1"] = map1; + map2["list"] = new List { 1, 2, 3 }; + + Dictionary map3 = new Dictionary(); + map3["map2"] = map2; + + Dictionary map4 = new Dictionary(); + map4["map3"] = map3; + + ConnectorAttributeBuilder.Build("map", map4); + } + } + #endregion +} diff --git a/dotnet/framework/FrameworkTests/ConnectorFacadeExceptionTests.cs b/dotnet/framework/FrameworkTests/ConnectorFacadeExceptionTests.cs new file mode 100644 index 00000000..af2130be --- /dev/null +++ b/dotnet/framework/FrameworkTests/ConnectorFacadeExceptionTests.cs @@ -0,0 +1,122 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using NUnit.Framework; +using System.Diagnostics; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; +using Org.IdentityConnectors.Test.Common; +using Org.IdentityConnectors.Common; + +namespace FrameworkTests +{ + [TestFixture] + public class ConnectorFacadeExceptionTests + { + /// + /// The test specific exception that is intended to be distinguished from the exceptions the framework + /// might throw. + /// + private class EUTestException : Exception + { + } + + private class SpyConnector : Connector, TestOp + { + #region Member variables + private static StackTrace _stackTrace; + #endregion + + #region Properties + /// + /// Gets the stack trace of the last call to the method performed on + /// any instance of this class. + /// + public static StackTrace StackTrace + { + get + { + return _stackTrace; + } + } + #endregion + + #region Connector Members + public void Init(Configuration configuration) + { + } + #endregion + + #region IDisposable Members + public void Dispose() + { + } + #endregion + + #region TestOp Members + public void Test() + { + //do not insert file info, as the stack trace of the exception and the dumped stack trace + //will always differ in the line numbers + _stackTrace = new StackTrace(false); + throw new EUTestException(); + } + #endregion + } + + private class MockConfiguration : AbstractConfiguration + { + public override void Validate() + { + } + } + + /// + /// Tests whether the implementation let the exception - thrown by a connector - + /// propagate to the caller with the call stack representation from the method that throws the exception. + /// + /// The current implementation uses reflection that can hide the original + /// exception. See for more information. + [Test] + public void TestStackTraceOfExceptionThrownByConnectorFacade() + { + ConnectorFacadeFactory factory = ConnectorFacadeFactory.GetInstance(); + Configuration config = new MockConfiguration(); + ConnectorFacade facade = factory.NewInstance( + TestHelpers.CreateTestConfiguration(SafeType.Get(), config)); + + try + { + facade.Test(); + + Assert.Fail("Exception was not thrown"); + } + catch (EUTestException eutex) + { + ExceptionUtilTestHelpers.AssertStackTrace(eutex, SpyConnector.StackTrace); + } + } + } +} diff --git a/dotnet/framework/FrameworkTests/ConnectorFacadeTests.cs b/dotnet/framework/FrameworkTests/ConnectorFacadeTests.cs new file mode 100644 index 00000000..17b596b1 --- /dev/null +++ b/dotnet/framework/FrameworkTests/ConnectorFacadeTests.cs @@ -0,0 +1,663 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ + +using System; +using System.Collections.Generic; + +using NUnit.Framework; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Test.Common; + +namespace FrameworkTests +{ + + [TestFixture] + public class ConnectorFacadeTests + { + + [SetUp] + public void setup() + { + // always reset the call patterns.. + MockConnector.Reset(); + } + + private class TestOperationPattern + { + /// + /// Simple call back to make the 'facade' calls. + /// + public Action MakeCall; + + /// + /// Given the list of calls determine if they match expected values based + /// on the calls made in the method. + /// + public Action> CheckCalls; + } + + /// + /// Test the pattern of the common operations. + /// + private void TestCallPattern(TestOperationPattern pattern) + { + TestCallPattern(pattern, SafeType.Get()); + } + + private void TestCallPattern(TestOperationPattern pattern, + SafeType clazz) + { + Configuration config = new MockConfiguration(false); + ConnectorFacadeFactory factory = ConnectorFacadeFactory.GetInstance(); + // **test only** + APIConfiguration impl = TestHelpers.CreateTestConfiguration(clazz, config); + ConnectorFacade facade; + facade = factory.NewInstance(impl); + // make the call on the connector facade.. + pattern.MakeCall(facade); + // check the call structure.. + IList calls = MockConnector.GetCallPattern(); + // check the call pattern.. + Assert.AreEqual("Init", calls[0].MethodName); + calls.RemoveAt(0); + pattern.CheckCalls(calls); + Assert.AreEqual("Dispose", calls[0].MethodName); + calls.RemoveAt(0); + Assert.IsTrue(calls.Count == 0); + } + + /// + /// Tests that if an SPI operation is not implemented that the API will throw + /// an . + /// + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void UnsupportedOperationTest() + { + Configuration config = new MockConfiguration(false); + SafeType clazz = SafeType.Get(); + ConnectorFacadeFactory factory = ConnectorFacadeFactory.GetInstance(); + APIConfiguration impl = TestHelpers.CreateTestConfiguration(clazz, + config); + ConnectorFacade facade; + facade = factory.NewInstance(impl); + facade.Authenticate(ObjectClass.ACCOUNT, "fadf", new GuardedString(), null); + } + + [Test] + public void RunScriptOnConnectorCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.RunScriptOnConnector( + new ScriptContextBuilder("lang", "script").Build(), + null); + }, + CheckCalls = calls => + { + Assert.AreEqual("RunScriptOnConnector", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + public void RunScriptOnResourceCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.RunScriptOnResource( + new ScriptContextBuilder("lang", "script").Build(), + null); + }, + CheckCalls = calls => + { + Assert.AreEqual("RunScriptOnResource", GetAndRemoveMethodName(calls)); + } + }); + } + + /// + /// Test the call pattern to get the schema. + /// + [Test] + public void SchemaCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.Schema(); + }, + CheckCalls = calls => + { + Assert.AreEqual("Schema", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + public void AuthenticateCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.Authenticate(ObjectClass.ACCOUNT, "dfadf", new GuardedString(), null); + }, + CheckCalls = calls => + { + Assert.AreEqual("Authenticate", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void AuthenticateAllCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.Authenticate(ObjectClass.ALL, "dfadf", new GuardedString(), null); + }, + CheckCalls = calls => + { + Assert.Fail("Should not get here.."); + } + }); + } + + [Test] + public void ResolveUsernameCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.ResolveUsername(ObjectClass.ACCOUNT, "dfadf", null); + }, + CheckCalls = calls => + { + Assert.AreEqual("ResolveUsername", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void ResolveUsernameAllCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.ResolveUsername(ObjectClass.ALL, "dfadf", null); + }, + CheckCalls = calls => + { + Assert.Fail("Should not get here.."); + } + }); + } + + [Test] + public void CreateCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + ICollection attrs = CollectionUtil.NewReadOnlySet(); + facade.Create(ObjectClass.ACCOUNT, attrs, null); + }, + CheckCalls = calls => + { + Assert.AreEqual("Create", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void CreateWithOutObjectClassPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + ICollection attrs = new HashSet(); + facade.Create(null, attrs, null); + }, + CheckCalls = calls => + { + Assert.Fail("Should not get here.."); + } + }); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void CreateAllCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + ICollection attrs = CollectionUtil.NewReadOnlySet(); + facade.Create(ObjectClass.ALL, attrs, null); + }, + CheckCalls = calls => + { + Assert.Fail("Should not get here.."); + } + }); + } + + [Test] + [ExpectedException(typeof(InvalidAttributeValueException))] + public void CreateDuplicatConnectorAttributesPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + ICollection attrs = new HashSet(); + attrs.Add(ConnectorAttributeBuilder.Build("abc", 1)); + attrs.Add(ConnectorAttributeBuilder.Build("abc", 2)); + facade.Create(ObjectClass.ACCOUNT, attrs, null); + }, + CheckCalls = calls => + { + Assert.Fail("Should not get here.."); + } + }); + } + + [Test] + public void UpdateCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + ICollection attrs = new HashSet(); + attrs.Add(ConnectorAttributeBuilder.Build("accountid")); + facade.Update(ObjectClass.ACCOUNT, NewUid(0), attrs, null); + }, + CheckCalls = calls => + { + Assert.AreEqual("Update", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void UpdateAllCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + ICollection attrs = new HashSet(); + attrs.Add(ConnectorAttributeBuilder.Build("accountid")); + facade.Update(ObjectClass.ALL, NewUid(0), attrs, null); + }, + CheckCalls = calls => + { + Assert.Fail("Should not get here.."); + } + }); + } + + [Test] + public void DeleteCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.Delete(ObjectClass.ACCOUNT, NewUid(0), null); + }, + CheckCalls = calls => + { + Assert.AreEqual("Delete", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void DeleteAllCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.Delete(ObjectClass.ALL, NewUid(0), null); + }, + CheckCalls = calls => + { + Assert.Fail("Should not get here.."); + } + }); + } + + [Test] + public void SearchCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + // create an empty results handler.. + ResultsHandler rh = new ResultsHandler() + { + Handle = obj => { + return true; + } + + }; + // call the search method.. + facade.Search(ObjectClass.ACCOUNT, null, rh, null); + }, + CheckCalls = calls => + { + Assert.AreEqual("CreateFilterTranslator", GetAndRemoveMethodName(calls)); + Assert.AreEqual("ExecuteQuery", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void SearchAllCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + // create an empty results handler.. + ResultsHandler rh = new ResultsHandler() + { + Handle = obj => + { + return true; + } + }; + // call the search method.. + facade.Search(ObjectClass.ALL, null, rh, null); + }, + CheckCalls = calls => + { + Assert.Fail("Should not get here.."); + } + }); + } + + [Test] + public void GetCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + // create an empty results handler.. + // call the search method.. + facade.GetObject(ObjectClass.ACCOUNT, NewUid(0), null); + }, + CheckCalls = calls => + { + Assert.AreEqual("CreateFilterTranslator", GetAndRemoveMethodName(calls)); + Assert.AreEqual("ExecuteQuery", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void GetAllCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + // create an empty results handler.. + // call the search method.. + facade.GetObject(ObjectClass.ALL, NewUid(0), null); + }, + CheckCalls = calls => + { + Assert.Fail("Should not get here.."); + } + }); + } + + [Test] + public virtual void GetLatestSyncTokenCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + // call the getLatestSyncToken method.. + facade.GetLatestSyncToken(ObjectClass.ALL); + }, + CheckCalls = calls => + { + Assert.AreEqual("GetLatestSyncToken", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + public virtual void GetLatestSyncTokenAllCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + // call the getLatestSyncToken method.. + facade.GetLatestSyncToken(ObjectClass.ALL); + }, + CheckCalls = calls => + { + Assert.AreEqual("GetLatestSyncToken", GetAndRemoveMethodName(calls)); + } + }); + } + + + [Test] + public virtual void SyncCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + // create an empty results handler.. + SyncResultsHandler rh = new SyncResultsHandler() + { + Handle = obj => + { + return true; + } + }; + // call the sync method.. + facade.Sync(ObjectClass.ACCOUNT, new SyncToken(1), rh, null); + }, + CheckCalls = calls => + { + Assert.AreEqual("Sync", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + public virtual void SyncAllCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + // create an empty results handler.. + SyncResultsHandler rh = new SyncResultsHandler() + { + Handle = obj => + { + return true; + } + }; + // call the sync method.. + facade.Sync(ObjectClass.ALL, new SyncToken(1), rh, null); + }, + CheckCalls = calls => + { + Assert.AreEqual("Sync", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + [ExpectedException(typeof(ConnectorException), ExpectedMessage="Sync '__ALL__' operation requires.*", MatchType = MessageMatch.Regex)] + public virtual void SyncAllCallFailPattern() { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + // create an empty results handler.. + SyncResultsHandler rh = new SyncResultsHandler() + { + Handle = obj => + { + return true; + } + }; + // call the sync method.. + var builder = new OperationOptionsBuilder(); + builder.Options["FAIL_DELETE"] = true; + facade.Sync(ObjectClass.ALL, new SyncToken(1), rh, builder.Build()); + }, + CheckCalls = calls => + { + Assert.AreEqual("Sync", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + public void TestOpCallPattern() + { + TestCallPattern(new TestOperationPattern() + { + MakeCall = facade => + { + facade.Test(); + }, + CheckCalls = calls => + { + Assert.AreEqual("Test", GetAndRemoveMethodName(calls)); + } + }); + } + + [Test] + public void UpdateMergeTests() + { + ConnectorAttribute expected, actual; + Configuration config = new MockConfiguration(false); + ConnectorFacadeFactory factory = ConnectorFacadeFactory.GetInstance(); + SafeType clazz = SafeType.Get(); + // **test only** + APIConfiguration impl = TestHelpers.CreateTestConfiguration(clazz, config); + impl.SetTimeout(SafeType.Get(), APIConstants.NO_TIMEOUT); + impl.SetTimeout(SafeType.Get(), APIConstants.NO_TIMEOUT); + impl.SetTimeout(SafeType.Get(), APIConstants.NO_TIMEOUT); + ConnectorFacade facade = factory.NewInstance(impl); + // sniff test to make sure we can get an object.. + ConnectorObject obj = facade.GetObject(ObjectClass.ACCOUNT, NewUid(1), null); + Assert.AreEqual(NewUid(1), obj.Uid); + // ok lets add an attribute that doesn't exist.. + String ADDED = "somthing to add to the object"; + String ATTR_NAME = "added"; + ICollection addAttrSet; + addAttrSet = CollectionUtil.NewSet((IEnumerable)obj.GetAttributes()); + addAttrSet.Add(ConnectorAttributeBuilder.Build(ATTR_NAME, ADDED)); + Name name = obj.Name; + addAttrSet.Remove(name); + Uid uid = facade.AddAttributeValues(ObjectClass.ACCOUNT, obj.Uid, ConnectorAttributeUtil.FilterUid(addAttrSet), null); + // get back the object and see if there are the same.. + addAttrSet.Add(name); + ConnectorObject addO = new ConnectorObject(ObjectClass.ACCOUNT, addAttrSet); + obj = facade.GetObject(ObjectClass.ACCOUNT, NewUid(1), null); + Assert.AreEqual(addO, obj); + // attempt to add on to an existing attribute.. + addAttrSet.Remove(name); + uid = facade.AddAttributeValues(ObjectClass.ACCOUNT, obj.Uid, ConnectorAttributeUtil.FilterUid(addAttrSet), null); + // get the object back out and check on it.. + obj = facade.GetObject(ObjectClass.ACCOUNT, uid, null); + expected = ConnectorAttributeBuilder.Build(ATTR_NAME, ADDED, ADDED); + actual = obj.GetAttributeByName(ATTR_NAME); + Assert.AreEqual(expected, actual); + // attempt to delete a value from an attribute.. + ICollection deleteAttrs = CollectionUtil.NewSet((IEnumerable)addO.GetAttributes()); + deleteAttrs.Remove(name); + uid = facade.RemoveAttributeValues(ObjectClass.ACCOUNT, addO.Uid, ConnectorAttributeUtil.FilterUid(deleteAttrs), null); + obj = facade.GetObject(ObjectClass.ACCOUNT, uid, null); + expected = ConnectorAttributeBuilder.Build(ATTR_NAME, ADDED); + actual = obj.GetAttributeByName(ATTR_NAME); + Assert.AreEqual(expected, actual); + // attempt to delete an attribute that doesn't exist.. + ICollection nonExist = new HashSet(); + nonExist.Add(NewUid(1)); + nonExist.Add(ConnectorAttributeBuilder.Build("does not exist", "asdfe")); + uid = facade.RemoveAttributeValues(ObjectClass.ACCOUNT, addO.Uid, ConnectorAttributeUtil.FilterUid(nonExist), null); + obj = facade.GetObject(ObjectClass.ACCOUNT, NewUid(1), null); + Assert.IsTrue(obj.GetAttributeByName("does not exist") == null); + } + + static Uid NewUid(int id) + { + return new Uid(Convert.ToString(id)); + } + + static string GetAndRemoveMethodName(IList calls) + { + string result = calls[0].MethodName; + calls.RemoveAt(0); + return result; + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/ConnectorInfoManagerTests.cs b/dotnet/framework/FrameworkTests/ConnectorInfoManagerTests.cs new file mode 100755 index 00000000..c29baf9e --- /dev/null +++ b/dotnet/framework/FrameworkTests/ConnectorInfoManagerTests.cs @@ -0,0 +1,1150 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using System.Collections; +using NUnit.Framework; +using System.Collections.Generic; +using System.Diagnostics; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Impl.Api.Remote; +using ICF = Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Server; +using Org.IdentityConnectors.Framework.Impl.Api; +using Org.IdentityConnectors.Framework.Impl.Api.Local; +using System.Threading; +using System.Globalization; +using System.Net.Security; +using System.Reactive.Linq; +using System.Security.Cryptography.X509Certificates; +using Org.IdentityConnectors.Test.Common; + +namespace FrameworkTests +{ + [TestFixture] + public class ConnectorInfoManagerTests + { + protected static ConnectorInfo FindConnectorInfo + (ConnectorInfoManager manager, + string version, + string connectorName) + { + foreach (ConnectorInfo info in manager.ConnectorInfos) + { + ConnectorKey key = info.ConnectorKey; + if (version.Equals(key.BundleVersion) && + connectorName.Equals(key.ConnectorName)) + { + //intentionally ineffecient to test + //more code + return manager.FindConnectorInfo(key); + } + } + return null; + } + + [TearDown] + public void TearDown() + { + ShutdownConnnectorInfoManager(); + } + + [Test] + public void TestClassLoading() + { + ConnectorInfoManager manager = + GetConnectorInfoManager(); + ConnectorInfo info1 = + FindConnectorInfo(manager, + "1.0.0.0", + "org.identityconnectors.testconnector.TstConnector"); + Assert.IsNotNull(info1); + ConnectorInfo info2 = + FindConnectorInfo(manager, + "2.0.0.0", + "org.identityconnectors.testconnector.TstConnector"); + + Assert.IsNotNull(info2); + + ConnectorFacade facade1 = + ConnectorFacadeFactory.GetInstance().NewInstance(info1.CreateDefaultAPIConfiguration()); + + ConnectorFacade facade2 = + ConnectorFacadeFactory.GetInstance().NewInstance(info2.CreateDefaultAPIConfiguration()); + + ICollection attrs = new HashSet(); + Assert.AreEqual("1.0", facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue()); + Assert.AreEqual("2.0", facade2.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue()); + } + + [Test] + public void TestAPIConfiguration() + { + ConnectorInfoManager manager = + GetConnectorInfoManager(); + ConnectorInfo info = + FindConnectorInfo(manager, + "1.0.0.0", + "org.identityconnectors.testconnector.TstConnector"); + Assert.IsNotNull(info); + APIConfiguration api = info.CreateDefaultAPIConfiguration(); + + ConfigurationProperties props = api.ConfigurationProperties; + ConfigurationProperty property = props.GetProperty("tstField"); + + Assert.IsNotNull(property); + ICollection> operations = + property.Operations; + Assert.AreEqual(1, operations.Count); + Assert.IsTrue(operations.Contains(SafeType.Get())); + + Thread.CurrentThread.CurrentUICulture = new CultureInfo("en"); + Assert.AreEqual("Help for test field.", property.GetHelpMessage(null)); + Assert.AreEqual("Display for test field.", property.GetDisplayName(null)); + Assert.AreEqual("Group for test field.", property.GetGroup(null)); + Assert.AreEqual("Test Framework Value", + info.Messages.Format("TEST_FRAMEWORK_KEY", "empty")); + + CultureInfo eslocale = new CultureInfo("es"); + Thread.CurrentThread.CurrentUICulture = eslocale; + Assert.AreEqual("tstField.help_es", property.GetHelpMessage(null)); + Assert.AreEqual("tstField.display_es", property.GetDisplayName(null)); + + CultureInfo esESlocale = new CultureInfo("es-ES"); + Thread.CurrentThread.CurrentUICulture = esESlocale; + Assert.AreEqual("tstField.help_es-ES", property.GetHelpMessage(null)); + Assert.AreEqual("tstField.display_es-ES", property.GetDisplayName(null)); + + CultureInfo esARlocale = new CultureInfo("es-AR"); + Thread.CurrentThread.CurrentUICulture = esARlocale; + Assert.AreEqual("tstField.help_es", property.GetHelpMessage(null)); + Assert.AreEqual("tstField.display_es", property.GetDisplayName(null)); + + ConnectorFacadeFactory facf = ConnectorFacadeFactory.GetInstance(); + ConnectorFacade facade = facf.NewInstance(api); + // call the various create/update/delete commands.. + facade.Schema(); + } + + [Test] + public void TestValidate() + { + ConnectorInfoManager manager = + GetConnectorInfoManager(); + ConnectorInfo info = + FindConnectorInfo(manager, + "1.0.0.0", + "org.identityconnectors.testconnector.TstConnector"); + Assert.IsNotNull(info); + APIConfiguration api = info.CreateDefaultAPIConfiguration(); + + ConfigurationProperties props = api.ConfigurationProperties; + ConfigurationProperty property = props.GetProperty("failValidation"); + property.Value = false; + ConnectorFacadeFactory facf = ConnectorFacadeFactory.GetInstance(); + ConnectorFacade facade = facf.NewInstance(api); + facade.Validate(); + property.Value = true; + facade = facf.NewInstance(api); + try + { + Thread.CurrentThread.CurrentUICulture = new CultureInfo("en"); + facade.Validate(); + Assert.Fail("exception expected"); + } + catch (ConnectorException e) + { + Assert.AreEqual("validation failed en", e.Message); + } + try + { + Thread.CurrentThread.CurrentUICulture = new CultureInfo("es"); + facade.Validate(); + Assert.Fail("exception expected"); + } + catch (ConnectorException e) + { + Assert.AreEqual("validation failed es", e.Message); + } + } + + /// + /// Main purpose of this is to test searching with + /// many results and that we can properly handle + /// stopping in the middle of this. + /// + /// + /// There's a bunch of + /// code in the remote stuff that is there to handle this + /// in particular that we want to excercise. + /// + [Test] + public void TestSearchWithManyResults() + { + ConnectorInfoManager manager = + GetConnectorInfoManager(); + ConnectorInfo info = + FindConnectorInfo(manager, + "1.0.0.0", + "org.identityconnectors.testconnector.TstConnector"); + Assert.IsNotNull(info); + APIConfiguration api = info.CreateDefaultAPIConfiguration(); + api.ProducerBufferSize = 0; + + ConfigurationProperties props = api.ConfigurationProperties; + ConfigurationProperty property = props.GetProperty("numResults"); + + //1000 is several times the remote size between pauses + property.Value = 1000; + + ConnectorFacadeFactory facf = ConnectorFacadeFactory.GetInstance(); + ConnectorFacade facade = facf.NewInstance(api); + + IList results = new List(); + + SearchResult searchResult = facade.Search(ObjectClass.ACCOUNT, null, new ResultsHandler() + { + Handle = + obj => + { + results.Add(obj); + return true; + } + }, null); + + Assert.AreEqual(1000, results.Count); + Assert.AreEqual(0, searchResult.RemainingPagedResults); + for (int i = 0; i < results.Count; i++) + { + ConnectorObject obj = results[i]; + Assert.AreEqual(i.ToString(), + obj.Uid.GetUidValue()); + } + + results.Clear(); + + searchResult = facade.Search(ObjectClass.ACCOUNT, null, new ResultsHandler() + { + Handle = obj => + { + if (results.Count < 500) + { + results.Add(obj); + return true; + } + else + { + return false; + } + } + }, null); + + Assert.AreEqual(500, results.Count); + Assert.IsTrue(500 == searchResult.RemainingPagedResults || 401 == searchResult.RemainingPagedResults); + for (int i = 0; i < results.Count; i++) + { + ConnectorObject obj = results[i]; + Assert.AreEqual(i.ToString(), + obj.Uid.GetUidValue()); + } + } + /// + /// Main purpose of this is to test sync with + /// many results and that we can properly handle + /// stopping in the middle of this. + /// + /// + /// There's a bunch of + /// code in the remote stuff that is there to handle this + /// in particular that we want to excercise. + /// + [Test] + public void TestSyncWithManyResults() + { + ConnectorInfoManager manager = + GetConnectorInfoManager(); + ConnectorInfo info = + FindConnectorInfo(manager, + "1.0.0.0", + "org.identityconnectors.testconnector.TstConnector"); + Assert.IsNotNull(info); + APIConfiguration api = info.CreateDefaultAPIConfiguration(); + + ConfigurationProperties props = api.ConfigurationProperties; + ConfigurationProperty property = props.GetProperty("numResults"); + + //1000 is several times the remote size between pauses + property.Value = (1000); + + ConnectorFacadeFactory facf = ConnectorFacadeFactory.GetInstance(); + ConnectorFacade facade = facf.NewInstance(api); + + SyncToken latest = facade.GetLatestSyncToken(ObjectClass.ACCOUNT); + Assert.AreEqual("mylatest", latest.Value); + IList results = new List(); + + facade.Sync(ObjectClass.ACCOUNT, null, new SyncResultsHandler() + { + Handle = obj => + { + results.Add(obj); + return true; + } + }, null); + + Assert.AreEqual(1000, results.Count); + for (int i = 0; i < results.Count; i++) + { + SyncDelta obj = results[i]; + Assert.AreEqual(i.ToString(), + obj.Uid.GetUidValue()); + } + + results.Clear(); + + facade.Sync(ObjectClass.ACCOUNT, + null, new SyncResultsHandler() + { + Handle = obj => + { + if (results.Count < 500) + { + results.Add(obj); + return true; + } + else + { + return false; + } + } + } + , null); + + Assert.AreEqual(500, results.Count); + for (int i = 0; i < results.Count; i++) + { + SyncDelta obj = results[i]; + Assert.AreEqual(i.ToString(), + obj.Uid.GetUidValue()); + } + } + + [Test] + public void TestSyncTokenResults() + { + foreach (ConnectorFacade facade in CreateStateFulFacades()) + { + Uid uid = facade.Create(ObjectClass.ACCOUNT, CollectionUtil.NewReadOnlySet(), null); + + SyncToken latest = facade.GetLatestSyncToken(ObjectClass.ACCOUNT); + Assert.AreEqual(uid.GetUidValue(), latest.Value); + + for (int i = 0; i < 10; i++) + { + SyncToken lastToken = facade.Sync(ObjectClass.ACCOUNT, null, new SyncResultsHandler() + { + Handle = obj => + { + return true; + } + }, null); + Assert.IsNotNull(lastToken); + Assert.AreEqual(lastToken.Value, latest.Value); + } + } + } + + [Test] + public void TestSubscriptionOperation() + { + foreach (ConnectorFacade facade in CreateStateFulFacades()) + { + if (facade is LocalConnectorFacadeImpl) + { + ToListResultsHandler handler = new ToListResultsHandler(); + CountdownEvent cde = new CountdownEvent(1); + var localFacade = facade; + var connectorObjectObservable = + Observable.Create(o => localFacade.Subscribe(ObjectClass.ACCOUNT, null, o, null)); + + var subscription = connectorObjectObservable.Subscribe( + co => + { + Console.WriteLine("Connector Event received:{0}", co.Uid.GetUidValue()); + handler.ResultsHandler.Handle(co); + }, + ex => + { + cde.Signal(); + Assert.AreEqual(handler.Objects.Count, 10, "Uncompleted subscription"); + }); + + + cde.Wait(new TimeSpan(0, 0, 25)); + subscription.Dispose(); + Assert.AreEqual(10, handler.Objects.Count); + + handler = new ToListResultsHandler(); + cde = new CountdownEvent(1); + + connectorObjectObservable = + Observable.Create( + o => + localFacade.Subscribe(ObjectClass.ACCOUNT, null, o, + OperationOptionsBuilder.Create().SetOption("doComplete", true).Build())); + + bool failed = false; + subscription = connectorObjectObservable.Subscribe( + co => + { + Console.WriteLine("Connector Event received:{0}", co.Uid.GetUidValue()); + handler.ResultsHandler.Handle(co); + }, + ex => + { + cde.Signal(); + failed = true; + }, () => + { + cde.Signal(); + }); + + + cde.Wait(new TimeSpan(0, 0, 25)); + subscription.Dispose(); + Assert.IsFalse(failed); + Assert.AreEqual(10, handler.Objects.Count); + + handler = new ToListResultsHandler(); + cde = new CountdownEvent(1); + + var syncDeltaObservable = + Observable.Create(o => localFacade.Subscribe(ObjectClass.ACCOUNT, null, o, null)); + + IDisposable[] subscriptions = new IDisposable[1]; + subscriptions[0] = syncDeltaObservable.Subscribe( + delta => + { + Console.WriteLine("Sync Event received:{0}", delta.Token.Value); + if (((int?)delta.Token.Value) > 2) + { + subscriptions[0].Dispose(); + cde.Signal(); + } + handler.ResultsHandler.Handle(delta.Object); + }, + ex => + { + cde.Signal(); + Assert.Fail("Failed Subscription", ex); + }); + + cde.Wait(new TimeSpan(0, 0, 25)); + for (int i = 0; i < 5 && !(handler.Objects.Count > 2); i++) + { + Console.WriteLine("Wait for result handler thread to complete: {0}", i); + Thread.Sleep(200); // Wait to complete all other threads + } + Assert.IsTrue(handler.Objects.Count < 10 && handler.Objects.Count > 2); + } + } + } + + [Test] + public void TestConnectionPooling() + { + ConnectorPoolManager.Dispose(); + ConnectorInfoManager manager = + GetConnectorInfoManager(); + ConnectorInfo info1 = + FindConnectorInfo(manager, + "1.0.0.0", + "org.identityconnectors.testconnector.TstConnector"); + Assert.IsNotNull(info1); + //reset connection count + { + //trigger TstConnection.init to be called + APIConfiguration config2 = + info1.CreateDefaultAPIConfiguration(); + config2.ConfigurationProperties.GetProperty("resetConnectionCount").Value = (true); + ConnectorFacade facade2 = + ConnectorFacadeFactory.GetInstance().NewInstance(config2); + facade2.Schema(); //force instantiation + } + + APIConfiguration config = + info1.CreateDefaultAPIConfiguration(); + + config.ConnectorPoolConfiguration.MinIdle = (0); + config.ConnectorPoolConfiguration.MaxIdle = (0); + + ConnectorFacade facade1 = + ConnectorFacadeFactory.GetInstance().NewInstance(config); + + OperationOptionsBuilder builder = new OperationOptionsBuilder(); + builder.SetOption("testPooling", "true"); + OperationOptions options = builder.Build(); + ICollection attrs = CollectionUtil.NewReadOnlySet(); + Assert.AreEqual("1", facade1.Create(ObjectClass.ACCOUNT, attrs, options).GetUidValue()); + Assert.AreEqual("2", facade1.Create(ObjectClass.ACCOUNT, attrs, options).GetUidValue()); + Assert.AreEqual("3", facade1.Create(ObjectClass.ACCOUNT, attrs, options).GetUidValue()); + Assert.AreEqual("4", facade1.Create(ObjectClass.ACCOUNT, attrs, options).GetUidValue()); + config = + info1.CreateDefaultAPIConfiguration(); + config.ConnectorPoolConfiguration.MinIdle = (1); + config.ConnectorPoolConfiguration.MaxIdle = (2); + facade1 = + ConnectorFacadeFactory.GetInstance().NewInstance(config); + Assert.AreEqual("5", facade1.Create(ObjectClass.ACCOUNT, attrs, options).GetUidValue()); + Assert.AreEqual("5", facade1.Create(ObjectClass.ACCOUNT, attrs, options).GetUidValue()); + Assert.AreEqual("5", facade1.Create(ObjectClass.ACCOUNT, attrs, options).GetUidValue()); + Assert.AreEqual("5", facade1.Create(ObjectClass.ACCOUNT, attrs, options).GetUidValue()); + } + + [Test] + public void TestScripting() + { + ConnectorInfoManager manager = + GetConnectorInfoManager(); + ConnectorInfo info = + FindConnectorInfo(manager, + "1.0.0.0", + "org.identityconnectors.testconnector.TstConnector"); + Assert.IsNotNull(info); + APIConfiguration api = info.CreateDefaultAPIConfiguration(); + + + ConnectorFacadeFactory facf = ConnectorFacadeFactory.GetInstance(); + ConnectorFacade facade = facf.NewInstance(api); + + ScriptContextBuilder builder = new ScriptContextBuilder(); + builder.AddScriptArgument("arg1", "value1"); + builder.AddScriptArgument("arg2", "value2"); + builder.ScriptLanguage = ("BOO"); + + //test that they can run the script and access the + //connector object + { + String SCRIPT = + "connector.concat(arg1,arg2)"; + builder.ScriptText = (SCRIPT); + String result = (String)facade.RunScriptOnConnector(builder.Build(), + null); + + Assert.AreEqual("value1value2", result); + } + + //test that they can access a class in the class loader + { + String SCRIPT = + "import org.identityconnectors.testconnector\n" + + "TstConnector.GetVersion()"; + builder.ScriptText = (SCRIPT); + String result = (String)facade.RunScriptOnConnector(builder.Build(), + null); + Assert.AreEqual("1.0", result); + } + + //test that they cannot access a class in internal + { + Type clazz = typeof(ConfigurationPropertyImpl); + + String SCRIPT = + "import " + clazz.Namespace + "\n" + + clazz.Name + "()"; + builder.ScriptText = (SCRIPT); + try + { + facade.RunScriptOnConnector(builder.Build(), + null); + Assert.Fail("exception expected"); + } + catch (Exception e) + { + String msg = e.Message; + String expectedMessage = + "Namespace '" + clazz.Namespace + "' not found"; + Assert.IsTrue( + msg.Contains(expectedMessage), + "Unexpected message: " + msg); + } + } + + // test that they can access a class in common + { + Type clazz = typeof(ConnectorAttributeBuilder); + String SCRIPT = + "import " + clazz.Namespace + "\n" + + clazz.Name + ".Build(\"myattr\")"; + builder.ScriptText = (SCRIPT); + ConnectorAttribute attr = (ConnectorAttribute)facade.RunScriptOnConnector(builder.Build(), null); + Assert.AreEqual("myattr", attr.Name); + } + } + + [Test] + public void TestConnectorContext() + { + ConnectorPoolManager.Dispose(); + ConnectorInfoManager manager = GetConnectorInfoManager(); + ConnectorInfo info1 = FindConnectorInfo(manager, "1.0.0.0", "org.identityconnectors.testconnector.TstStatefulConnector"); + Assert.IsNotNull(info1); + + APIConfiguration config = info1.CreateDefaultAPIConfiguration(); + + config.ConnectorPoolConfiguration.MinIdle = 0; + config.ConnectorPoolConfiguration.MaxIdle = 0; + + ConnectorFacade facade1 = ConnectorFacadeFactory.GetInstance().NewInstance(config); + + ICollection attrs = CollectionUtil.NewReadOnlySet(); + string uid = facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + + config = info1.CreateDefaultAPIConfiguration(); + config.ConnectorPoolConfiguration.MinIdle = 1; + config.ConnectorPoolConfiguration.MaxIdle = 2; + facade1 = ConnectorFacadeFactory.GetInstance().NewInstance(config); + uid = facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + Assert.AreEqual(facade1.Create(ObjectClass.ACCOUNT, attrs, null).GetUidValue(), uid); + } + + [Test] + public void TestAttributeTypeMap() + { + ConnectorPoolManager.Dispose(); + ConnectorInfoManager manager = GetConnectorInfoManager(); + ConnectorInfo info1 = FindConnectorInfo(manager, "1.0.0.0", "org.identityconnectors.testconnector.TstStatefulConnector"); + Assert.IsNotNull(info1); + + APIConfiguration config = info1.CreateDefaultAPIConfiguration(); + + config.ConnectorPoolConfiguration.MinIdle = 0; + config.ConnectorPoolConfiguration.MaxIdle = 0; + + ConnectorFacade facade = ConnectorFacadeFactory.GetInstance().NewInstance(config); + + HashSet createAttributes = new HashSet(); + IDictionary mapAttribute = new Dictionary(); + mapAttribute["email"] = "foo@example.com"; + mapAttribute["primary"] = true; + mapAttribute["usage"] = new List() { "home", "work" }; + createAttributes.Add(ConnectorAttributeBuilder.Build("emails", mapAttribute)); + + Uid uid = facade.Create(ObjectClass.ACCOUNT, createAttributes, null); + Assert.AreEqual(uid.GetUidValue(), "foo@example.com"); + + ConnectorObject co = facade.GetObject(ObjectClass.ACCOUNT, new Uid("0"), null); + object value = ConnectorAttributeUtil.GetSingleValue(co.GetAttributeByName("emails")); + Assert.IsTrue(value is IDictionary); + Assert.IsTrue(((IDictionary)value)["usage"] is IList); + } + + [Test] + public void TestPagedSearch() + { + ConnectorPoolManager.Dispose(); + ConnectorInfoManager manager = GetConnectorInfoManager(); + ConnectorInfo info1 = FindConnectorInfo(manager, "1.0.0.0", "org.identityconnectors.testconnector.TstStatefulPoolableConnector"); + Assert.IsNotNull(info1); + + APIConfiguration config = info1.CreateDefaultAPIConfiguration(); + config.ProducerBufferSize = 0; + + config.ConnectorPoolConfiguration.MinIdle = 1; + config.ConnectorPoolConfiguration.MaxIdle = 2; + + ConnectorFacade facade1 = ConnectorFacadeFactory.GetInstance().NewInstance(config); + + OperationOptionsBuilder builder = new OperationOptionsBuilder(); + builder.PageSize = 10; + builder.SetSortKeys(new ICF.SortKey(Name.NAME, true)); + + SearchResult searchResult = null; + ISet UIDs = new HashSet(); + + int iteration = 0; + do + { + + if (null != searchResult) + { + builder.PagedResultsCookie = searchResult.PagedResultsCookie; + } + + int size = 0; + searchResult = facade1.Search(ObjectClass.ACCOUNT, null, new ResultsHandler() + { + + Handle = obj => + { + if (size >= 10) + { + Assert.Fail("More then 10 objects was handled!"); + } + size++; + if (UIDs.Contains(obj.Uid)) + { + Assert.Fail("Duplicate Entry in results"); + } + return UIDs.Add(obj.Uid); + } + }, builder.Build()); + iteration++; + Assert.IsNotNull(searchResult); + Assert.AreEqual(searchResult.RemainingPagedResults, 100 - (iteration * 10)); + Assert.AreEqual(searchResult.TotalPagedResultsPolicy, SearchResult.CountPolicy.EXACT); + Assert.AreEqual(searchResult.TotalPagedResults, 100); + + } while (searchResult.PagedResultsCookie != null); + + // Search with paged results offset + + builder = new OperationOptionsBuilder(); + builder.PageSize = 10; + builder.PagedResultsOffset = 5; + builder.SetSortKeys(new ICF.SortKey(Name.NAME, true)); + + searchResult = null; + + UIDs.Clear(); + Filter filter = FilterBuilder.EqualTo(ConnectorAttributeBuilder.BuildEnabled(true)); + + iteration = 0; + do + { + + if (null != searchResult) + { + builder.PagedResultsCookie = searchResult.PagedResultsCookie; + } + + int size = 0; + searchResult = facade1.Search(ObjectClass.ACCOUNT, filter, new ResultsHandler() + { + Handle = obj => + { + if (size >= 10) + { + Assert.Fail("More then 10 objects was handled!"); + } + size++; + if (UIDs.Contains(obj.Uid)) + { + Assert.Fail("Duplicate Entry in results"); + } + return UIDs.Add(obj.Uid); + } + }, builder.Build()); + iteration++; + Assert.IsNotNull(searchResult); + Assert.AreEqual(searchResult.RemainingPagedResults, Math.Max(50 - (iteration * 15), 0)); + + } while (searchResult.PagedResultsCookie != null); + } + + [Test] + public void TestTimeout() + { + ConnectorInfoManager manager = GetConnectorInfoManager(); + ConnectorInfo info1 = FindConnectorInfo(manager, "1.0.0.0", "org.identityconnectors.testconnector.TstConnector"); + Assert.IsNotNull(info1); + + APIConfiguration config = info1.CreateDefaultAPIConfiguration(); + config.SetTimeout(SafeType.ForRawType(typeof(CreateApiOp)), 5000); + config.SetTimeout(SafeType.ForRawType(typeof(SearchApiOp)), 5000); + ConfigurationProperties props = config.ConfigurationProperties; + ConfigurationProperty property = props.GetProperty("numResults"); + // 1000 is several times the remote size between pauses + property.Value = 2; + OperationOptionsBuilder opBuilder = new OperationOptionsBuilder(); + opBuilder.SetOption("delay", 10000); + + ConnectorFacade facade1 = ConnectorFacadeFactory.GetInstance().NewInstance(config); + + ICollection attrs = CollectionUtil.NewReadOnlySet(); + try + { + facade1.Create(ObjectClass.ACCOUNT, attrs, opBuilder.Build()).GetUidValue(); + Assert.Fail("expected timeout"); + } + catch (OperationTimeoutException) + { + // expected + } + //catch (RemoteWrappedException e) + //{ + // Assert.IsTrue(e.Is(typeof(OperationTimeoutException))); + //} + + try + { + facade1.Search(ObjectClass.ACCOUNT, null, new ResultsHandler() + { + Handle = obj => + { + return true; + } + }, opBuilder.Build()); + Assert.Fail("expected timeout"); + } + catch (OperationTimeoutException) + { + // expected + } + //catch (RemoteWrappedException e) + //{ + // Assert.IsTrue(e.Is(typeof(OperationTimeoutException))); + //} + } + + [Test] + public void TestMVCCControl() + { + + foreach (ConnectorFacade facade in CreateStateFulFacades()) + { + + + Uid uid = facade.Create(ObjectClass.ACCOUNT, CollectionUtil.NewReadOnlySet(), null); + + + if (facade is LocalConnectorFacadeImpl) + { + try + { + facade.Delete(ObjectClass.ACCOUNT, uid, null); + } + catch (PreconditionRequiredException) + { + // Expected + } + catch (Exception) + { + Assert.Fail("Expecting PreconditionRequiredException"); + } + try + { + facade.Delete(ObjectClass.ACCOUNT, new Uid(uid.GetUidValue(), "0"), null); + } + catch (PreconditionFailedException) + { + // Expected + } + catch (Exception) + { + Assert.Fail("Expecting PreconditionFailedException"); + } + facade.Delete(ObjectClass.ACCOUNT, new Uid(uid.GetUidValue(), uid.GetUidValue()), null); + } + else + { + try + { + facade.Delete(ObjectClass.ACCOUNT, uid, null); + } + catch (RemoteWrappedException e) + { + if (!e.Is(typeof(PreconditionRequiredException))) + { + Assert.Fail("Expecting PreconditionRequiredException"); + } + } + catch (Exception) + { + Assert.Fail("Expecting RemoteWrappedException"); + } + try + { + facade.Delete(ObjectClass.ACCOUNT, new Uid(uid.GetUidValue(), "0"), null); + } + catch (RemoteWrappedException e) + { + if (!e.Is(typeof(PreconditionFailedException))) + { + Assert.Fail("Expecting PreconditionFailedException"); + } + } + catch (Exception) + { + Assert.Fail("Expecting RemoteWrappedException"); + } + facade.Delete(ObjectClass.ACCOUNT, new Uid(uid.GetUidValue(), uid.GetUidValue()), null); + } + } + } + + public IList CreateStateFulFacades() + { + IList test = new List(2); + + ConnectorInfoManager manager = GetConnectorInfoManager(); + ConnectorInfo info = FindConnectorInfo(manager, "1.0.0.0", "org.identityconnectors.testconnector.TstStatefulConnector"); + Assert.IsNotNull(info); + + APIConfiguration config = info.CreateDefaultAPIConfiguration(); + + test.Add(ConnectorFacadeFactory.GetInstance().NewInstance(config)); + + info = FindConnectorInfo(manager, "1.0.0.0", "org.identityconnectors.testconnector.TstStatefulPoolableConnector"); + Assert.IsNotNull(info); + + config = info.CreateDefaultAPIConfiguration(); + + config.ConnectorPoolConfiguration.MinIdle = 0; + config.ConnectorPoolConfiguration.MaxIdle = 0; + + test.Add(ConnectorFacadeFactory.GetInstance().NewInstance(config)); + + return test; + } + + protected virtual ConnectorInfoManager GetConnectorInfoManager() + { + ConnectorInfoManagerFactory fact = ConnectorInfoManagerFactory.GetInstance(); + ConnectorInfoManager manager = fact.GetLocalManager(); + return manager; + } + + protected virtual void ShutdownConnnectorInfoManager() + { + ConnectorFacadeFactory.GetInstance().Dispose(); + } + } + + [TestFixture] + public class RemoteConnectorInfoManagerClearTests : ConnectorInfoManagerTests + { + + private ConnectorServer _server; + + protected override ConnectorInfoManager GetConnectorInfoManager() + { + TestUtil.InitializeLogging(); + + GuardedString str = new GuardedString(); + str.AppendChar('c'); + str.AppendChar('h'); + str.AppendChar('a'); + str.AppendChar('n'); + str.AppendChar('g'); + str.AppendChar('e'); + str.AppendChar('i'); + str.AppendChar('t'); + +#if DEBUG + const int PORT = 58758; +#else + const int PORT = 58759; +#endif + _server = ConnectorServer.NewInstance(); + _server.Port = PORT; + _server.IfAddress = (IOUtil.GetIPAddress("127.0.0.1")); + _server.KeyHash = str.GetBase64SHA1Hash(); + _server.Start(); + //while ( true ) { + // Thread.Sleep(1000); + //} + ConnectorInfoManagerFactory fact = ConnectorInfoManagerFactory.GetInstance(); + + RemoteFrameworkConnectionInfo connInfo = new + RemoteFrameworkConnectionInfo("127.0.0.1", PORT, str); + + ConnectorInfoManager manager = fact.GetRemoteManager(connInfo); + + return manager; + } + + protected override void ShutdownConnnectorInfoManager() + { + if (_server != null) + { + _server.Stop(); + _server = null; + } + } + + [Test, Explicit] + [Category("LongRunning")] + public virtual void TestFacadeEviction() + { + ConnectorServer server = ConnectorServer.NewInstance(); + try + { + GuardedString str = new GuardedString(); + str.AppendChar('c'); + str.AppendChar('h'); + str.AppendChar('a'); + str.AppendChar('n'); + str.AppendChar('g'); + str.AppendChar('e'); + str.AppendChar('i'); + str.AppendChar('t'); + +#if DEBUG + const int PORT = 58760; +#else + const int PORT = 58761; +#endif + + server.MaxFacadeLifeTime = 1; + server.Port = PORT; + server.IfAddress = (IOUtil.GetIPAddress("127.0.0.1")); + server.KeyHash = str.GetBase64SHA1Hash(); + server.Start(); + + RemoteFrameworkConnectionInfo connInfo = + new RemoteFrameworkConnectionInfo("127.0.0.1", PORT, str, false, null, 0); + ConnectorInfoManager remoteManager = + ConnectorInfoManagerFactory.GetInstance().GetRemoteManager(connInfo); + + ConnectorInfo remoteInfo = + FindConnectorInfo(remoteManager, "1.0.0.0", "org.identityconnectors.testconnector.TstConnector"); + + ConnectorFacade remoteFacade = ConnectorFacadeFactory.GetInstance(). + NewInstance(remoteInfo.CreateDefaultAPIConfiguration()); + + ManagedConnectorFacadeFactoryImpl managedFactory = + (ManagedConnectorFacadeFactoryImpl)ConnectorFacadeFactory.GetManagedInstance(); + + // Assert it's empty + Assert.IsNull(managedFactory.Find(remoteFacade.ConnectorFacadeKey)); + remoteFacade.Schema(); + // Assert it has one item + Assert.IsNotNull(managedFactory.Find(remoteFacade.ConnectorFacadeKey)); + Thread.Sleep(new TimeSpan(0, 2, 0)); + // Assert it's empty + Assert.IsNull(managedFactory.Find(remoteFacade.ConnectorFacadeKey)); + } + finally + { + server.Stop(); + } + } + } + + internal class MyCertificateValidationCallback + { + public bool Validate(Object sender, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + Trace.TraceInformation("validating: " + certificate.Subject); + return true; + } + } + + [TestFixture] + public class RemoteConnectorInfoManagerSSLTests : ConnectorInfoManagerTests + { + + //To generate test certificate do the following: + // + //makecert -r -pe -n "CN=localhost" -ss TestCertificateStore -sr currentuser -sky exchange + // + //In MMC, go to the certificate, export + private ConnectorServer _server; + private const String CERT_PATH = "../../../server.pfx"; + protected override ConnectorInfoManager GetConnectorInfoManager() + { + TestUtil.InitializeLogging(); + + GuardedString str = new GuardedString(); + str.AppendChar('c'); + str.AppendChar('h'); + str.AppendChar('a'); + str.AppendChar('n'); + str.AppendChar('g'); + str.AppendChar('e'); + str.AppendChar('i'); + str.AppendChar('t'); + +#if DEBUG + const int PORT = 58762; +#else + const int PORT = 58761; +#endif + + /*X509Store store = new X509Store("TestCertificateStore", + StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadOnly|OpenFlags.OpenExistingOnly); + X509Certificate certificate = store.Certificates[0]; + store.Close();*/ + + X509Certificate2 certificate = new + X509Certificate2(CERT_PATH, + "changeit"); + //Trace.TraceInformation("certificate: "+certificate); + _server = ConnectorServer.NewInstance(); + _server.Port = PORT; + _server.KeyHash = str.GetBase64SHA1Hash(); + _server.IfAddress = (IOUtil.GetIPAddress("localhost")); + _server.UseSSL = true; + _server.ServerCertificate = certificate; + _server.Start(); + //while ( true ) { + // Thread.Sleep(1000); + //} + ConnectorInfoManagerFactory fact = ConnectorInfoManagerFactory.GetInstance(); + MyCertificateValidationCallback + callback = new MyCertificateValidationCallback(); + RemoteFrameworkConnectionInfo connInfo = new + RemoteFrameworkConnectionInfo("localhost", + PORT, + str, + true, + callback.Validate, + 60000); + + ConnectorInfoManager manager = fact.GetRemoteManager(connInfo); + + return manager; + } + + protected override void ShutdownConnnectorInfoManager() + { + if (_server != null) + { + _server.Stop(); + _server = null; + } + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/ExceptionUtilTests.cs b/dotnet/framework/FrameworkTests/ExceptionUtilTests.cs new file mode 100644 index 00000000..5159c149 --- /dev/null +++ b/dotnet/framework/FrameworkTests/ExceptionUtilTests.cs @@ -0,0 +1,114 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using NUnit.Framework; +using System.Diagnostics; +using Org.IdentityConnectors.Framework.Impl; + +namespace FrameworkTests +{ + /// + /// Contains helper function to verify the output of . + /// + internal class ExceptionUtilTestHelpers + { + /// + /// Tests whether the is originated from the same function as in which the + /// was captured. + /// + /// The exception to test. + /// The captured stack trace to test against. + public static void AssertStackTrace(Exception exception, StackTrace stackTrace) + { + Trace.TraceInformation("Stack trace from the failing method: {0}", stackTrace.ToString()); + Trace.TraceInformation("Exception stack trace: {0}", exception.StackTrace); + + string fullST = stackTrace.ToString(); + int newLinePos = fullST.IndexOf(Environment.NewLine); + string failingMethodExpected = fullST.Substring(0, (newLinePos == -1) ? fullST.Length : newLinePos); + + newLinePos = exception.StackTrace.IndexOf(Environment.NewLine); + string failingMethodActual = exception.StackTrace.Substring(0, (newLinePos == -1) ? fullST.Length : newLinePos); + + //check if the first line of the stack trace fetched from the failing method is contained in the + //first line of the exception's stack trace, i.e. the method which threw the exception is in the + //stack trace of the exception + Assert.That(failingMethodActual.Contains(failingMethodExpected)); + } + } + + [TestFixture] + public class ExceptionUtilTests + { + [Test] + public void TestPreserveStackTrace() + { + StackTrace stackTrace = null; + try + { + try + { + Action bar = () => + { + try + { + //delegate to the failing method + Action foo = () => + { + stackTrace = new StackTrace(false); + throw new InvalidOperationException(); + }; + + foo(); + + Assert.Fail("Exception was not thrown - 1"); + } + catch (InvalidOperationException iopex) + { + //wrap the exception to make sure that there is an + //inner exception that can be re-thrown later on + throw new ArgumentException(string.Empty, iopex); + }; + }; + + bar(); + + Assert.Fail("Exception was not thrown - 2"); + } + catch (ArgumentException aex) + { + //preserve the stack trace of the nested exception and re-throw it + ExceptionUtil.PreserveStackTrace(aex.InnerException); + throw aex.InnerException; + } + + Assert.Fail("Exception was not thrown - 3"); + } + catch (InvalidOperationException iopex) + { + ExceptionUtilTestHelpers.AssertStackTrace(iopex, stackTrace); + } + } + } +} diff --git a/dotnet/framework/FrameworkTests/FilterTranslatorTests.cs b/dotnet/framework/FrameworkTests/FilterTranslatorTests.cs new file mode 100644 index 00000000..173646c8 --- /dev/null +++ b/dotnet/framework/FrameworkTests/FilterTranslatorTests.cs @@ -0,0 +1,642 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +namespace FrameworkTests +{ + + internal class AllFiltersTranslator : AbstractFilterTranslator + { + protected override String CreateAndExpression(String leftExpression, String rightExpression) + { + return "( & " + leftExpression + " " + rightExpression + " )"; + } + + protected override String CreateOrExpression(String leftExpression, String rightExpression) + { + return "( | " + leftExpression + " " + rightExpression + " )"; + } + + protected override String CreateContainsExpression(ContainsFilter filter, bool not) + { + String rv = "( CONTAINS " + filter.GetName() + " " + filter.GetValue() + " )"; + return Not(rv, not); + } + + protected override String CreateEndsWithExpression(EndsWithFilter filter, bool not) + { + String rv = "( ENDS-WITH " + filter.GetName() + " " + filter.GetValue() + " )"; + return Not(rv, not); + } + + protected override String CreateEqualsExpression(EqualsFilter filter, bool not) + { + String rv = "( = " + filter.GetAttribute().Name + " [" + filter.GetAttribute().Value[0] + "] )"; + return Not(rv, not); + } + + protected override String CreateGreaterThanExpression(GreaterThanFilter filter, bool not) + { + String rv = "( > " + filter.GetName() + " " + filter.GetValue() + " )"; + return Not(rv, not); + } + + protected override String CreateGreaterThanOrEqualExpression(GreaterThanOrEqualFilter filter, bool not) + { + String rv = "( >= " + filter.GetName() + " " + filter.GetValue() + " )"; + return Not(rv, not); + } + + protected override String CreateLessThanExpression(LessThanFilter filter, bool not) + { + String rv = "( < " + filter.GetName() + " " + filter.GetValue() + " )"; + return Not(rv, not); + } + + protected override String CreateLessThanOrEqualExpression(LessThanOrEqualFilter filter, bool not) + { + String rv = "( <= " + filter.GetName() + " " + filter.GetValue() + " )"; + return Not(rv, not); + } + + protected override String CreateStartsWithExpression(StartsWithFilter filter, bool not) + { + String rv = "( STARTS-WITH " + filter.GetName() + " " + filter.GetValue() + " )"; + return Not(rv, not); + } + protected override String CreateContainsAllValuesExpression(ContainsAllValuesFilter filter, bool not) + { + String rv = "( CONTAINS-ALL-VALUES " + filter.GetAttribute() + " )"; + return Not(rv, not); + } + private String Not(String orig, bool not) + { + if (not) + { + return "( ! " + orig + " )"; + } + else + { + return orig; + } + } + } + + /// + /// Everything but Or + /// + internal class NoOrTranslator : AllFiltersTranslator + { + protected override String CreateOrExpression(String leftExpression, String rightExpression) + { + return null; + } + } + /// + /// Everything but EndsWith + /// + internal class NoEndsWithTranslator : AllFiltersTranslator + { + protected override String CreateEndsWithExpression(EndsWithFilter filter, bool not) + { + return null; + } + } + /// + /// Everything but EndsWith,Or + /// + internal class NoEndsWithNoOrTranslator : AllFiltersTranslator + { + protected override String CreateOrExpression(String leftExpression, String rightExpression) + { + return null; + } + protected override String CreateEndsWithExpression(EndsWithFilter filter, bool not) + { + return null; + } + } + + /// + /// Everything but And + /// + internal class NoAndTranslator : AllFiltersTranslator + { + protected override String CreateAndExpression(String leftExpression, String rightExpression) + { + return null; + } + } + /// + /// Everything but And + /// + internal class NoAndNoEndsWithTranslator : AllFiltersTranslator + { + protected override String CreateAndExpression(String leftExpression, String rightExpression) + { + return null; + } + protected override String CreateEndsWithExpression(EndsWithFilter filter, bool not) + { + return null; + } + + } + /// + /// Everything but And + /// + internal class NoAndNoOrNoEndsWithTranslator : AllFiltersTranslator + { + protected override String CreateAndExpression(String leftExpression, String rightExpression) + { + return null; + } + protected override String CreateEndsWithExpression(EndsWithFilter filter, bool not) + { + return null; + } + protected override String CreateOrExpression(String leftExpression, String rightExpression) + { + return null; + } + + } + [TestFixture] + public class FilterTranslatorTests + { + + /// + /// Test all operations when everything is fully implemented. + /// + /// + /// Test not normalization as well. + /// + [Test] + public void TestBasics() + { + ConnectorAttribute attribute = + ConnectorAttributeBuilder.Build("att-name", "att-value"); + ConnectorAttribute attribute2 = + ConnectorAttributeBuilder.Build("att-name2", "att-value2"); + AllFiltersTranslator translator = new + AllFiltersTranslator(); + + { + Filter filter = + FilterBuilder.Contains(attribute); + String expected = "( CONTAINS att-name att-value )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expected = "( ! " + expected + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + { + Filter filter = + FilterBuilder.EndsWith(attribute); + String expected = "( ENDS-WITH att-name att-value )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expected = "( ! " + expected + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + { + Filter filter = + FilterBuilder.EqualTo(attribute); + String expected = "( = att-name [att-value] )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expected = "( ! " + expected + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + { + Filter filter = + FilterBuilder.GreaterThan(attribute); + String expected = "( > att-name att-value )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expected = "( ! " + expected + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + { + Filter filter = + FilterBuilder.GreaterThanOrEqualTo(attribute); + String expected = "( >= att-name att-value )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expected = "( ! " + expected + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + { + Filter filter = + FilterBuilder.LessThan(attribute); + String expected = "( < att-name att-value )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expected = "( ! " + expected + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + { + Filter filter = + FilterBuilder.LessThanOrEqualTo(attribute); + String expected = "( <= att-name att-value )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expected = "( ! " + expected + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + { + Filter filter = + FilterBuilder.StartsWith(attribute); + String expected = "( STARTS-WITH att-name att-value )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expected = "( ! " + expected + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + { + Filter filter = + FilterBuilder.ContainsAllValues(attribute); + String expected = "( CONTAINS-ALL-VALUES " + attribute + " )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expected = "( ! " + expected + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + //and + { + Filter left = + FilterBuilder.Contains(attribute); + Filter right = + FilterBuilder.Contains(attribute2); + String expectedLeft = "( CONTAINS att-name att-value )"; + String expectedRight = "( CONTAINS att-name2 att-value2 )"; + Filter filter = + FilterBuilder.And(left, right); + String expected = + "( & " + expectedLeft + " " + expectedRight + " )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expectedLeft = "( ! " + expectedLeft + " )"; + expectedRight = "( ! " + expectedRight + " )"; + expected = + "( | " + expectedLeft + " " + expectedRight + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + } + + //or + { + Filter left = + FilterBuilder.Contains(attribute); + Filter right = + FilterBuilder.Contains(attribute2); + String expectedLeft = "( CONTAINS att-name att-value )"; + String expectedRight = "( CONTAINS att-name2 att-value2 )"; + Filter filter = + FilterBuilder.Or(left, right); + String expected = + "( | " + expectedLeft + " " + expectedRight + " )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + + filter = FilterBuilder.Not(filter); + expectedLeft = "( ! " + expectedLeft + " )"; + expectedRight = "( ! " + expectedRight + " )"; + expected = + "( & " + expectedLeft + " " + expectedRight + " )"; + actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + //double-negative + { + Filter filter = + FilterBuilder.Contains(attribute); + filter = FilterBuilder.Not(filter); + filter = FilterBuilder.Not(filter); + String expected = "( CONTAINS att-name att-value )"; + String actual = + TranslateSingle(translator, filter); + Assert.AreEqual(expected, actual); + } + + } + + /// + /// (a OR b) AND ( c OR d) needs to become + /// (a AND c) OR ( a AND d) OR (b AND c) OR (b AND d) is + /// OR is not implemented. + /// + /// + /// Otherwise it should stay + /// as-is. + /// + [Test] + public void TestDistribution() + { + Filter a = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("a", "a")); + Filter b = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("b", "b")); + Filter c = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("c", "c")); + Filter d = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("d", "d")); + + Filter filter = + FilterBuilder.And( + FilterBuilder.Or(a, b), + FilterBuilder.Or(c, d)); + String expected = "( & ( | ( CONTAINS a a ) ( CONTAINS b b ) ) ( | ( CONTAINS c c ) ( CONTAINS d d ) ) )"; + String actual = + TranslateSingle(new AllFiltersTranslator(), filter); + + Assert.AreEqual(expected, actual); + + IList results = + new NoOrTranslator().Translate(filter); + Assert.AreEqual(4, results.Count); + + Assert.AreEqual("( & ( CONTAINS a a ) ( CONTAINS c c ) )", + results[0]); + Assert.AreEqual("( & ( CONTAINS a a ) ( CONTAINS d d ) )", + results[1]); + Assert.AreEqual("( & ( CONTAINS b b ) ( CONTAINS c c ) )", + results[2]); + Assert.AreEqual("( & ( CONTAINS b b ) ( CONTAINS d d ) )", + results[3]); + } + + //test simplification + //-no leaf + [Test] + public void TestSimplifyNoLeaf() + { + Filter a = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("a", "a")); + Filter b = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("b", "b")); + Filter c = + FilterBuilder.EndsWith(ConnectorAttributeBuilder.Build("c", "c")); + Filter d = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("d", "d")); + + Filter filter = + FilterBuilder.And( + FilterBuilder.Or(a, b), + FilterBuilder.Or(c, d)); + String expected = "( | ( CONTAINS a a ) ( CONTAINS b b ) )"; + String actual = + TranslateSingle(new NoEndsWithTranslator(), filter); + Assert.AreEqual(expected, actual); + + } + //-no leaf + no or + [Test] + public void TestSimplifyNoLeafNoOr() + { + Filter a = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("a", "a")); + Filter b = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("b", "b")); + Filter c = + FilterBuilder.EndsWith(ConnectorAttributeBuilder.Build("c", "c")); + Filter d = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("d", "d")); + + Filter filter = + FilterBuilder.And( + FilterBuilder.Or(a, b), + FilterBuilder.Or(c, d)); + IList results = + new NoEndsWithNoOrTranslator().Translate(filter); + Assert.AreEqual(2, results.Count); + Assert.AreEqual("( CONTAINS a a )", + results[0]); + Assert.AreEqual("( CONTAINS b b )", + results[1]); + + } + + //-no and + [Test] + public void TestSimplifyNoAnd() + { + Filter a = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("a", "a")); + Filter b = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("b", "b")); + Filter c = + FilterBuilder.EndsWith(ConnectorAttributeBuilder.Build("c", "c")); + Filter d = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("d", "d")); + + Filter filter = + FilterBuilder.And( + FilterBuilder.Or(a, b), + FilterBuilder.Or(c, d)); + String expected = "( | ( CONTAINS a a ) ( CONTAINS b b ) )"; + String actual = + TranslateSingle(new NoAndTranslator(), filter); + Assert.AreEqual(expected, actual); + } + + //-no and+no leaf + [Test] + public void TestSimplifyNoAndNoLeaf() + { + Filter a = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("a", "a")); + Filter b = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("b", "b")); + Filter c = + FilterBuilder.EndsWith(ConnectorAttributeBuilder.Build("c", "c")); + Filter d = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("d", "d")); + + Filter filter = + FilterBuilder.And( + FilterBuilder.Or(a, b), + FilterBuilder.Or(c, d)); + String expected = "( | ( CONTAINS a a ) ( CONTAINS b b ) )"; + String actual = + TranslateSingle(new NoAndNoEndsWithTranslator(), filter); + Assert.AreEqual(expected, actual); + + a = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("a", "a")); + b = + FilterBuilder.EndsWith(ConnectorAttributeBuilder.Build("b", "b")); + c = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("c", "c")); + d = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("d", "d")); + + filter = + FilterBuilder.And( + FilterBuilder.Or(a, b), + FilterBuilder.Or(c, d)); + expected = "( | ( CONTAINS c c ) ( CONTAINS d d ) )"; + actual = + TranslateSingle(new NoAndNoEndsWithTranslator(), filter); + Assert.AreEqual(expected, actual); + + a = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("a", "a")); + b = + FilterBuilder.EndsWith(ConnectorAttributeBuilder.Build("b", "b")); + c = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("c", "c")); + d = + FilterBuilder.EndsWith(ConnectorAttributeBuilder.Build("d", "d")); + + filter = + FilterBuilder.And( + FilterBuilder.Or(a, b), + FilterBuilder.Or(c, d)); + IList results = + new NoAndNoEndsWithTranslator().Translate(filter); + Assert.AreEqual(0, results.Count); + } + + //-no and, no or, no leaf + [Test] + public void TestSimplifyNoAndNoOrNoLeaf() + { + Filter a = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("a", "a")); + Filter b = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("b", "b")); + Filter c = + FilterBuilder.EndsWith(ConnectorAttributeBuilder.Build("c", "c")); + Filter d = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("d", "d")); + + Filter filter = + FilterBuilder.And( + FilterBuilder.Or(a, b), + FilterBuilder.Or(c, d)); + IList results = + new NoAndNoOrNoEndsWithTranslator().Translate(filter); + Assert.AreEqual(2, results.Count); + Assert.AreEqual("( CONTAINS a a )", + results[0]); + Assert.AreEqual("( CONTAINS b b )", + results[1]); + + a = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("a", "a")); + b = + FilterBuilder.EndsWith(ConnectorAttributeBuilder.Build("b", "b")); + c = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("c", "c")); + d = + FilterBuilder.Contains(ConnectorAttributeBuilder.Build("d", "d")); + filter = + FilterBuilder.And( + FilterBuilder.Or(a, b), + FilterBuilder.Or(c, d)); + results = + new NoAndNoOrNoEndsWithTranslator().Translate(filter); + Assert.AreEqual(2, results.Count); + Assert.AreEqual("( CONTAINS c c )", + results[0]); + Assert.AreEqual("( CONTAINS d d )", + results[1]); + } + + private static String TranslateSingle(AbstractFilterTranslator translator, + Filter filter) + { + IList translated = + translator.Translate(filter); + Assert.AreEqual(1, translated.Count); + return translated[0]; + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/FrameworkTests.csproj b/dotnet/framework/FrameworkTests/FrameworkTests.csproj new file mode 100644 index 00000000..175b9506 --- /dev/null +++ b/dotnet/framework/FrameworkTests/FrameworkTests.csproj @@ -0,0 +1,191 @@ + + + + + {32804F5A-812C-4FA6-835C-BDAE5B24D355} + Debug + AnyCPU + Library + FrameworkTests + FrameworkTests + FrameworkTests + v4.5.2 + + + + bin\Debug\ + True + Full + False + True + DEBUG;TRACE + + + false + + + bin\Release\ + true + pdbonly + True + False + TRACE + false + + + + + + + 4.0 + + + + 4.0 + + + False + ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + + + False + ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + + + False + ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + + + False + ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll + + + + 4.0 + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + Always + + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + {8B24461B-456A-4032-89A1-CD418F7B5B62} + Framework + + + {5B011775-B121-4EEE-A410-BA2D2F5BFB8B} + FrameworkInternal + + + {E6A207D2-E083-41BF-B522-D9D3EC09323E} + TestCommon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/GuardedByteArrayTests.cs b/dotnet/framework/FrameworkTests/GuardedByteArrayTests.cs new file mode 100644 index 00000000..381d44e2 --- /dev/null +++ b/dotnet/framework/FrameworkTests/GuardedByteArrayTests.cs @@ -0,0 +1,102 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Serializer; +using NUnit.Framework; + +namespace FrameworkTests +{ + [TestFixture] + public class GuardedByteArrayTests + { + [Test] + public void TestBasics() + { + GuardedByteArray ss = new GuardedByteArray(); + ss.AppendByte(0x00); + ss.AppendByte(0x01); + ss.AppendByte(0x02); + byte[] decrypted = DecryptToByteArray(ss); + Assert.AreEqual(new byte[] { 0x00, 0x01, 0x02 }, decrypted); + String hash = ss.GetBase64SHA1Hash(); + Assert.IsTrue(ss.VerifyBase64SHA1Hash(hash)); + ss.AppendByte(0x03); + Assert.IsFalse(ss.VerifyBase64SHA1Hash(hash)); + } + [Test] + public void TestRange() + { + + for (byte i = 0; i < 0xFF; i++) + { + byte expected = i; + GuardedByteArray gba = new GuardedByteArray(); + gba = (GuardedByteArray)SerializerUtil.CloneObject(gba); + gba.AppendByte(i); + gba.Access(new GuardedByteArray.LambdaAccessor(clearChars => + { + int v = (byte)clearChars[0]; + Assert.AreEqual(expected, v); + })); + + } + } + + [Test] + public void TestEquals() + { + GuardedByteArray arr1 = new GuardedByteArray(); + GuardedByteArray arr2 = new GuardedByteArray(); + Assert.AreEqual(arr1, arr2); + arr2.AppendByte(0x02); + Assert.AreNotEqual(arr1, arr2); + arr1.AppendByte(0x02); + Assert.AreEqual(arr1, arr2); + } + + + /// + /// Highly insecure method! Do not do this in production + /// code. + /// + /// + /// This is only for test purposes + /// + private byte[] DecryptToByteArray(GuardedByteArray str) + { + byte[] result = null; + str.Access(new GuardedByteArray.LambdaAccessor( + array => + { + result = new byte[array.Length]; + for (int i = 0; i < array.Length; i++) + { + result[i] = array[i]; + } + })); + return result; + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/GuardedStringTests.cs b/dotnet/framework/FrameworkTests/GuardedStringTests.cs new file mode 100644 index 00000000..0d0f0136 --- /dev/null +++ b/dotnet/framework/FrameworkTests/GuardedStringTests.cs @@ -0,0 +1,105 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Text; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Serializer; +using NUnit.Framework; + +namespace FrameworkTests +{ + [TestFixture] + public class GuardedStringTests + { + [Test] + public void TestBasics() + { + GuardedString ss = new GuardedString(); + ss.AppendChar('f'); + ss.AppendChar('o'); + ss.AppendChar('o'); + ss.AppendChar('b'); + ss.AppendChar('a'); + ss.AppendChar('r'); + String decrypted = DecryptToString(ss); + Assert.AreEqual("foobar", decrypted); + String hash = ss.GetBase64SHA1Hash(); + Assert.IsTrue(ss.VerifyBase64SHA1Hash(hash)); + ss.AppendChar('2'); + Assert.IsFalse(ss.VerifyBase64SHA1Hash(hash)); + } + [Test] + public void TestUnicode() + { + + for (int i = 0; i < 0xFFFF; i++) + { + int expected = i; + char c = (char)i; + GuardedString gs = new GuardedString(); + gs = (GuardedString)SerializerUtil.CloneObject(gs); + gs.AppendChar(c); + gs.Access(new GuardedString.LambdaAccessor(clearChars => + { + int v = (int)clearChars[0]; + Assert.AreEqual(expected, v); + })); + + } + } + + [Test] + public void TestEquals() + { + GuardedString str1 = new GuardedString(); + GuardedString str2 = new GuardedString(); + Assert.AreEqual(str1, str2); + str2.AppendChar('2'); + Assert.AreNotEqual(str1, str2); + str1.AppendChar('2'); + Assert.AreEqual(str1, str2); + } + + /// + /// Highly insecure method! Do not do this in production + /// code. + /// + /// + /// This is only for test purposes + /// + private String DecryptToString(GuardedString str) + { + StringBuilder buf = new StringBuilder(); + str.Access(new GuardedString.LambdaAccessor( + array => + { + for (int i = 0; i < array.Length; i++) + { + buf.Append(array[i]); + } + })); + return buf.ToString(); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/LocaleTests.cs b/dotnet/framework/FrameworkTests/LocaleTests.cs new file mode 100644 index 00000000..6f139239 --- /dev/null +++ b/dotnet/framework/FrameworkTests/LocaleTests.cs @@ -0,0 +1,210 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using System; +using NUnit.Framework; +using System.Globalization; +using System.Collections.Generic; +using Org.IdentityConnectors.Common; +namespace FrameworkTests +{ + [TestFixture] + public class LocaleTests + { + + + [Test] + public void TestJava2CSharp() + { + HashSet + cultures = new HashSet(CultureInfo.GetCultures(CultureTypes.AllCultures)); + + TestJavaLocale(cultures, new Locale("ar", "", ""), "Arabic"); + TestJavaLocale(cultures, new Locale("be", "", ""), "Belarusian"); + TestJavaLocale(cultures, new Locale("bg", "", ""), "Bulgarian"); + TestJavaLocale(cultures, new Locale("ca", "", ""), "Catalan"); + TestJavaLocale(cultures, new Locale("cs", "", ""), "Czech"); + TestJavaLocale(cultures, new Locale("da", "", ""), "Danish"); + TestJavaLocale(cultures, new Locale("de", "", ""), "German"); + TestJavaLocale(cultures, new Locale("el", "", ""), "Greek"); + TestJavaLocale(cultures, new Locale("en", "", ""), "English"); + TestJavaLocale(cultures, new Locale("es", "", ""), "Spanish"); + TestJavaLocale(cultures, new Locale("et", "", ""), "Estonian"); + TestJavaLocale(cultures, new Locale("fi", "", ""), "Finnish"); + TestJavaLocale(cultures, new Locale("fr", "", ""), "French"); + TestJavaLocale(cultures, new Locale("hr", "", ""), "Croatian"); + TestJavaLocale(cultures, new Locale("hu", "", ""), "Hungarian"); + TestJavaLocale(cultures, new Locale("is", "", ""), "Icelandic"); + TestJavaLocale(cultures, new Locale("it", "", ""), "Italian"); + TestJavaLocale(cultures, new Locale("iw", "", ""), "Hebrew"); + TestJavaLocale(cultures, new Locale("ja", "", ""), "Japanese"); + TestJavaLocale(cultures, new Locale("ko", "", ""), "Korean"); + TestJavaLocale(cultures, new Locale("lt", "", ""), "Lithuanian"); + TestJavaLocale(cultures, new Locale("lv", "", ""), "Latvian"); + TestJavaLocale(cultures, new Locale("mk", "", ""), "Macedonian"); + TestJavaLocale(cultures, new Locale("nl", "", ""), "Dutch"); + TestJavaLocale(cultures, new Locale("no", "", ""), "Norwegian"); + TestJavaLocale(cultures, new Locale("pl", "", ""), "Polish"); + TestJavaLocale(cultures, new Locale("pt", "", ""), "Portuguese"); + TestJavaLocale(cultures, new Locale("ro", "", ""), "Romanian"); + TestJavaLocale(cultures, new Locale("ru", "", ""), "Russian"); + TestJavaLocale(cultures, new Locale("sk", "", ""), "Slovak"); + TestJavaLocale(cultures, new Locale("sl", "", ""), "Slovenian"); + TestJavaLocale(cultures, new Locale("sq", "", ""), "Albanian"); + TestJavaLocale(cultures, new Locale("sr", "", ""), "Serbian"); + TestJavaLocale(cultures, new Locale("sv", "", ""), "Swedish"); + TestJavaLocale(cultures, new Locale("th", "", ""), "Thai"); + TestJavaLocale(cultures, new Locale("tr", "", ""), "Turkish"); + TestJavaLocale(cultures, new Locale("uk", "", ""), "Ukrainian"); + TestJavaLocale(cultures, new Locale("vi", "", ""), "Vietnamese"); + TestJavaLocale(cultures, new Locale("zh", "", ""), "Chinese"); + TestJavaLocale(cultures, new Locale("ar", "AE", ""), "Arabic (United Arab Emirates)"); + TestJavaLocale(cultures, new Locale("ar", "BH", ""), "Arabic (Bahrain)"); + TestJavaLocale(cultures, new Locale("ar", "DZ", ""), "Arabic (Algeria)"); + TestJavaLocale(cultures, new Locale("ar", "EG", ""), "Arabic (Egypt)"); + TestJavaLocale(cultures, new Locale("ar", "IQ", ""), "Arabic (Iraq)"); + TestJavaLocale(cultures, new Locale("ar", "JO", ""), "Arabic (Jordan)"); + TestJavaLocale(cultures, new Locale("ar", "KW", ""), "Arabic (Kuwait)"); + TestJavaLocale(cultures, new Locale("ar", "LB", ""), "Arabic (Lebanon)"); + TestJavaLocale(cultures, new Locale("ar", "LY", ""), "Arabic (Libya)"); + TestJavaLocale(cultures, new Locale("ar", "MA", ""), "Arabic (Morocco)"); + TestJavaLocale(cultures, new Locale("ar", "OM", ""), "Arabic (Oman)"); + TestJavaLocale(cultures, new Locale("ar", "QA", ""), "Arabic (Qatar)"); + TestJavaLocale(cultures, new Locale("ar", "SA", ""), "Arabic (Saudi Arabia)"); + TestJavaLocale(cultures, + new Locale("ar", "SD", ""), + "Arabic (Sudan)", + new Locale("ar")); + TestJavaLocale(cultures, new Locale("ar", "SY", ""), "Arabic (Syria)"); + TestJavaLocale(cultures, new Locale("ar", "TN", ""), "Arabic (Tunisia)"); + TestJavaLocale(cultures, new Locale("ar", "YE", ""), "Arabic (Yemen)"); + TestJavaLocale(cultures, new Locale("be", "BY", ""), "Belarusian (Belarus)"); + TestJavaLocale(cultures, new Locale("bg", "BG", ""), "Bulgarian (Bulgaria)"); + TestJavaLocale(cultures, new Locale("ca", "ES", ""), "Catalan (Spain)"); + TestJavaLocale(cultures, new Locale("cs", "CZ", ""), "Czech (Czech Republic)"); + TestJavaLocale(cultures, new Locale("da", "DK", ""), "Danish (Denmark)"); + TestJavaLocale(cultures, new Locale("de", "AT", ""), "German (Austria)"); + TestJavaLocale(cultures, new Locale("de", "CH", ""), "German (Switzerland)"); + TestJavaLocale(cultures, new Locale("de", "DE", ""), "German (Germany)"); + TestJavaLocale(cultures, new Locale("de", "LU", ""), "German (Luxembourg)"); + TestJavaLocale(cultures, new Locale("el", "GR", ""), "Greek (Greece)"); + TestJavaLocale(cultures, new Locale("en", "AU", ""), "English (Australia)"); + TestJavaLocale(cultures, new Locale("en", "CA", ""), "English (Canada)"); + TestJavaLocale(cultures, new Locale("en", "GB", ""), "English (United Kingdom)"); + TestJavaLocale(cultures, new Locale("en", "IE", ""), "English (Ireland)"); + TestJavaLocale(cultures, new Locale("en", "IN", ""), "English (India)"); + TestJavaLocale(cultures, new Locale("en", "NZ", ""), "English (New Zealand)"); + TestJavaLocale(cultures, new Locale("en", "US", ""), "English (United States)"); + TestJavaLocale(cultures, new Locale("en", "ZA", ""), "English (South Africa)"); + TestJavaLocale(cultures, new Locale("es", "AR", ""), "Spanish (Argentina)"); + TestJavaLocale(cultures, new Locale("es", "BO", ""), "Spanish (Bolivia)"); + TestJavaLocale(cultures, new Locale("es", "CL", ""), "Spanish (Chile)"); + TestJavaLocale(cultures, new Locale("es", "CO", ""), "Spanish (Colombia)"); + TestJavaLocale(cultures, new Locale("es", "CR", ""), "Spanish (Costa Rica)"); + TestJavaLocale(cultures, new Locale("es", "DO", ""), "Spanish (Dominican Republic)"); + TestJavaLocale(cultures, new Locale("es", "EC", ""), "Spanish (Ecuador)"); + TestJavaLocale(cultures, new Locale("es", "ES", ""), "Spanish (Spain)"); + TestJavaLocale(cultures, new Locale("es", "GT", ""), "Spanish (Guatemala)"); + TestJavaLocale(cultures, new Locale("es", "HN", ""), "Spanish (Honduras)"); + TestJavaLocale(cultures, new Locale("es", "MX", ""), "Spanish (Mexico)"); + TestJavaLocale(cultures, new Locale("es", "NI", ""), "Spanish (Nicaragua)"); + TestJavaLocale(cultures, new Locale("es", "PA", ""), "Spanish (Panama)"); + TestJavaLocale(cultures, new Locale("es", "PE", ""), "Spanish (Peru)"); + TestJavaLocale(cultures, new Locale("es", "PR", ""), "Spanish (Puerto Rico)"); + TestJavaLocale(cultures, new Locale("es", "PY", ""), "Spanish (Paraguay)"); + TestJavaLocale(cultures, new Locale("es", "SV", ""), "Spanish (El Salvador)"); + TestJavaLocale(cultures, new Locale("es", "UY", ""), "Spanish (Uruguay)"); + TestJavaLocale(cultures, new Locale("es", "VE", ""), "Spanish (Venezuela)"); + TestJavaLocale(cultures, new Locale("et", "EE", ""), "Estonian (Estonia)"); + TestJavaLocale(cultures, new Locale("fi", "FI", ""), "Finnish (Finland)"); + TestJavaLocale(cultures, new Locale("fr", "BE", ""), "French (Belgium)"); + TestJavaLocale(cultures, new Locale("fr", "CA", ""), "French (Canada)"); + TestJavaLocale(cultures, new Locale("fr", "CH", ""), "French (Switzerland)"); + TestJavaLocale(cultures, new Locale("fr", "FR", ""), "French (France)"); + TestJavaLocale(cultures, new Locale("fr", "LU", ""), "French (Luxembourg)"); + TestJavaLocale(cultures, new Locale("hi", "IN", ""), "Hindi (India)"); + TestJavaLocale(cultures, new Locale("hr", "HR", ""), "Croatian (Croatia)"); + TestJavaLocale(cultures, new Locale("hu", "HU", ""), "Hungarian (Hungary)"); + TestJavaLocale(cultures, new Locale("is", "IS", ""), "Icelandic (Iceland)"); + TestJavaLocale(cultures, new Locale("it", "CH", ""), "Italian (Switzerland)"); + TestJavaLocale(cultures, new Locale("it", "IT", ""), "Italian (Italy)"); + TestJavaLocale(cultures, new Locale("iw", "IL", ""), "Hebrew (Israel)"); + TestJavaLocale(cultures, new Locale("ja", "JP", ""), "Japanese (Japan)"); + TestJavaLocale(cultures, new Locale("ko", "KR", ""), "Korean (South Korea)"); + TestJavaLocale(cultures, new Locale("lt", "LT", ""), "Lithuanian (Lithuania)"); + TestJavaLocale(cultures, new Locale("lv", "LV", ""), "Latvian (Latvia)"); + TestJavaLocale(cultures, new Locale("mk", "MK", ""), "Macedonian (Macedonia)"); + TestJavaLocale(cultures, new Locale("nl", "BE", ""), "Dutch (Belgium)"); + TestJavaLocale(cultures, new Locale("nl", "NL", ""), "Dutch (Netherlands)"); + TestJavaLocale(cultures, new Locale("no", "NO", ""), "Norwegian (Norway)"); + TestJavaLocale(cultures, new Locale("no", "NO", "NY"), "Norwegian (Norway,Nynorsk)"); + TestJavaLocale(cultures, new Locale("pl", "PL", ""), "Polish (Poland)"); + TestJavaLocale(cultures, new Locale("pt", "BR", ""), "Portuguese (Brazil)"); + TestJavaLocale(cultures, new Locale("pt", "PT", ""), "Portuguese (Portugal)"); + TestJavaLocale(cultures, new Locale("ro", "RO", ""), "Romanian (Romania)"); + TestJavaLocale(cultures, new Locale("ru", "RU", ""), "Russian (Russia)"); + TestJavaLocale(cultures, new Locale("sk", "SK", ""), "Slovak (Slovakia)"); + TestJavaLocale(cultures, new Locale("sl", "SI", ""), "Slovenian (Slovenia)"); + TestJavaLocale(cultures, new Locale("sq", "AL", ""), "Albanian (Albania)"); + TestJavaLocale(cultures, new Locale("sr", "BA", ""), + "Serbian (Bosnia and Herzegovina)", + new Locale("sr")); + TestJavaLocale(cultures, new Locale("sr", "CS", ""), + "Serbian (Serbia and Montenegro)", + new Locale("sr")); + TestJavaLocale(cultures, new Locale("sv", "SE", ""), "Swedish (Sweden)"); + TestJavaLocale(cultures, new Locale("th", "TH", ""), "Thai (Thailand)"); + TestJavaLocale(cultures, new Locale("th", "TH", "TH"), + "Thai (Thailand,TH)", + new Locale("th", "TH")); + TestJavaLocale(cultures, new Locale("tr", "TR", ""), "Turkish (Turkey)"); + TestJavaLocale(cultures, new Locale("uk", "UA", ""), "Ukrainian (Ukraine)"); + TestJavaLocale(cultures, new Locale("vi", "VN", ""), "Vietnamese (Vietnam)"); + TestJavaLocale(cultures, new Locale("zh", "CN", ""), "Chinese (China)"); + TestJavaLocale(cultures, new Locale("zh", "HK", ""), "Chinese (Hong Kong)"); + TestJavaLocale(cultures, new Locale("zh", "TW", ""), "Chinese (Taiwan)"); + + //foreach (CultureInfo info in cultures) { + // Console.WriteLine("remaining: "+info+" "+info.DisplayName+" "+info.TwoLetterISOLanguageName); + //} + } + private void TestJavaLocale(HashSet cultures, + Locale original, + String display) + { + TestJavaLocale(cultures, original, display, null); + } + private void TestJavaLocale(HashSet cultures, + Locale original, + String display, + Locale expected) + { + if (expected == null) + { + expected = original; + } + CultureInfo cinfo = original.ToCultureInfo(); + Locale actual = Locale.FindLocale(cinfo); + Assert.AreEqual(expected, actual, display + " (" + original + ") " + " didn't map"); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/MockConnector.cs b/dotnet/framework/FrameworkTests/MockConnector.cs new file mode 100644 index 00000000..b802801a --- /dev/null +++ b/dotnet/framework/FrameworkTests/MockConnector.cs @@ -0,0 +1,379 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ + +using System; +using System.Collections.Generic; + +using NUnit.Framework; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; + +namespace FrameworkTests +{ + + public class MockConnector : Connector, SchemaOp + { + + /// + /// Represents a call to a connector method. + /// + public class Call + { + + private readonly string methodName; + private readonly object[] args; + + public Call(string methodName, params object[] args) + { + this.methodName = methodName; + this.args = args; + } + + public string MethodName + { + get + { + return methodName; + } + } + + public object[] Arguments + { + get + { + return this.args; + } + } + } + + // need to keep track of when methods are called an their parameters.. + private static IList callPattern = new List(); + + private Configuration _config; + + public void Dispose() + { + AddCall("Dispose"); + } + + public Schema Schema() + { + AddCall("Schema"); + return null; + } + + public void Init(Configuration cfg) + { + _config = cfg; + AddCall("Init", cfg); + } + + public Configuration getConfiguration() + { + return _config; + } + + /// + /// Clear the call pattern. + /// + public static void Reset() + { + callPattern.Clear(); + } + + /// + /// Get the current call pattern. + /// + public static IList GetCallPattern() + { + return CollectionUtil.NewList(callPattern); + } + + /// + /// Adds the call to the internal call pattern. + /// + public static void AddCall(string methodName, params object[] args) + { + callPattern.Add(new Call(methodName, args)); + } + } + + public class MockAllOpsConnector : MockConnector, CreateOp, + DeleteOp, UpdateOp, SearchOp, UpdateAttributeValuesOp, AuthenticateOp, + ResolveUsernameOp, TestOp, ScriptOnConnectorOp, ScriptOnResourceOp, SyncOp + { + + public object RunScriptOnConnector(ScriptContext request, + OperationOptions options) + { + Assert.IsNotNull(request); + Assert.IsNotNull(options); + AddCall("RunScriptOnConnector", request, options); + return null; + } + + public object RunScriptOnResource(ScriptContext request, + OperationOptions options) + { + Assert.IsNotNull(request); + Assert.IsNotNull(options); + AddCall("RunScriptOnResource", request, options); + return null; + } + + public Uid Create(ObjectClass oclass, ICollection attrs, + OperationOptions options) + { + Assert.IsNotNull(attrs); + AddCall("Create", attrs); + return null; + } + + public void Delete(ObjectClass objClass, Uid uid, + OperationOptions options) + { + Assert.IsNotNull(uid); + Assert.IsNotNull(objClass); + AddCall("Delete", objClass, uid); + } + + public Uid Update(ObjectClass objclass, Uid uid, ICollection attrs, + OperationOptions options) + { + Assert.IsNotNull(objclass); + Assert.IsNotNull(attrs); + AddCall("Update", objclass, attrs); + return null; + } + + public Uid AddAttributeValues(ObjectClass objclass, Uid uid, + ICollection valuesToAdd, OperationOptions options) + { + AddCall("AddAttributeValues", objclass, valuesToAdd); + return null; + } + + public Uid RemoveAttributeValues(ObjectClass objclass, Uid uid, + ICollection valuesToRemove, OperationOptions options) + { + AddCall("RemoveAttributeValues", objclass, valuesToRemove); + return null; + } + + public FilterTranslator CreateFilterTranslator(ObjectClass oclass, + OperationOptions options) + { + Assert.IsNotNull(oclass); + Assert.IsNotNull(options); + AddCall("CreateFilterTranslator", oclass, options); + // no translation - ok since this is just for tests + return new MockFilterTranslator(); + } + + public void ExecuteQuery(ObjectClass oclass, string query, + ResultsHandler handler, OperationOptions options) + { + Assert.IsNotNull(oclass); + Assert.IsNotNull(handler); + Assert.IsNotNull(options); + AddCall("ExecuteQuery", oclass, query, handler, options); + if (null != options.PageSize && options.PageSize > 0) + { + // This is a pages search request + ((SearchResultsHandler)handler).HandleResult(new SearchResult("TOKEN==", 100)); + } + } + + public Uid Authenticate(ObjectClass objectClass, string username, GuardedString password, + OperationOptions options) + { + Assert.IsNotNull(username); + Assert.IsNotNull(password); + AddCall("Authenticate", username, password); + return null; + } + + public Uid ResolveUsername(ObjectClass objectClass, string username, OperationOptions options) + { + Assert.IsNotNull(username); + AddCall("ResolveUsername", username); + return null; + } + + public void Test() + { + AddCall("Test"); + } + + public void Sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler, OperationOptions options) + { + Assert.IsNotNull(objectClass); + Assert.IsNotNull(token); + Assert.IsNotNull(handler); + Assert.IsNotNull(options); + AddCall("Sync", objectClass, token, handler, options); + if (ObjectClass.ALL.Equals(objectClass)) + { + if (null != CollectionUtil.GetValue(options.Options, "FAIL_DELETE", null)) + { + //Require ObjectClass when delta is 'delete' + var builder = new SyncDeltaBuilder(); + builder.DeltaType = SyncDeltaType.DELETE; + builder.Uid = new Uid("DELETED"); + builder.Token = new SyncToken(99); + handler.Handle(builder.Build()); + } + else + { + ((SyncTokenResultsHandler)handler).HandleResult(new SyncToken(100)); + } + } + } + + public SyncToken GetLatestSyncToken(ObjectClass objectClass) + { + Assert.IsNotNull(objectClass); + AddCall("GetLatestSyncToken", objectClass); + return new SyncToken(0); + } + } + + public class MockUpdateConnector : Connector, UpdateOp, SearchOp + { + + private Configuration _cfg; + + public void Dispose() + { + // nothing to do this is a mock connector.. + } + + public void Init(Configuration cfg) + { + _cfg = cfg; + } + + public Configuration GetConfiguration() + { + return _cfg; + } + + private static IList objects = new List(); + + static MockUpdateConnector() + { + ConnectorObjectBuilder bld = new ConnectorObjectBuilder(); + for (int i = 0; i < 100; i++) + { + bld.SetUid(Convert.ToString(i)); + bld.SetName(Convert.ToString(i)); + objects.Add(bld.Build()); + } + } + + /// + /// This will do a basic replace. + /// + /// + /// + /// + public Uid Update(ObjectClass objclass, Uid uid, ICollection attrs, OperationOptions options) + { + string val = ConnectorAttributeUtil.GetAsStringValue(uid); + int idx = Convert.ToInt32(val); + //.Get out the object.. + ConnectorObject baseObject = objects[idx]; + ConnectorObjectBuilder bld = new ConnectorObjectBuilder(); + bld.Add(baseObject); + bld.AddAttributes(attrs); + ConnectorObject obj = bld.Build(); + objects[idx] = obj; + return obj.Uid; + } + + public FilterTranslator CreateFilterTranslator(ObjectClass oclass, OperationOptions options) + { + //no translation - ok since this is just for tests + return new MockFilterTranslator(); + } + + /// + /// Simply return everything don't bother optimizing. + /// + /// + /// + public void ExecuteQuery(ObjectClass oclass, string query, ResultsHandler handler, OperationOptions options) + { + foreach (ConnectorObject obj in objects) + { + if (!handler.Handle(obj)) + { + break; + } + } + } + } + + class MockFilterTranslator : AbstractFilterTranslator + { + } + + public class MockConfiguration : AbstractConfiguration + { + + private readonly bool fail; + + public MockConfiguration() + { + } + + /// + /// Determines if this configuration will fail validation. + /// + public MockConfiguration(bool failvalidation) + { + this.fail = failvalidation; + } + + public bool Fail + { + get; + set; + } + + public override void Validate() + { + if (fail) + { + throw new ConfigurationException(); + } + } + + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/ObjectClassUtilTests.cs b/dotnet/framework/FrameworkTests/ObjectClassUtilTests.cs new file mode 100644 index 00000000..2c75c3c9 --- /dev/null +++ b/dotnet/framework/FrameworkTests/ObjectClassUtilTests.cs @@ -0,0 +1,45 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using NUnit.Framework; +using Org.IdentityConnectors.Framework.Common.Objects; + +namespace FrameworkTests +{ + [TestFixture] + public class ObjectClassUtilTests + { + + [Test] + public void TestIsSpecial() + { + Assert.IsTrue(ObjectClassUtil.IsSpecial(ObjectClass.ACCOUNT)); + Assert.IsFalse(ObjectClassUtil.IsSpecial(new ObjectClass("o"))); + } + + [Test] + public void TestNamesEqual() + { + Assert.IsTrue(ObjectClassUtil.NamesEqual("ACCOUNT", "account")); + } + } +} diff --git a/dotnet/framework/FrameworkTests/ObjectNormalizerFacadeTests.cs b/dotnet/framework/FrameworkTests/ObjectNormalizerFacadeTests.cs new file mode 100644 index 00000000..21377a6d --- /dev/null +++ b/dotnet/framework/FrameworkTests/ObjectNormalizerFacadeTests.cs @@ -0,0 +1,250 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using System; +using NUnit.Framework; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Impl.Api.Local.Operations; +namespace FrameworkTests +{ + [TestFixture] + public class ObjectNormalizerFacadeTests + { + public class MyAttributeNormalizer : AttributeNormalizer + { + public ConnectorAttribute NormalizeAttribute(ObjectClass oclass, ConnectorAttribute attribute) + { + if (attribute.Is("foo")) + { + String val = ConnectorAttributeUtil.GetStringValue(attribute); + return ConnectorAttributeBuilder.Build("foo", val.Trim()); + } + else + { + return attribute; + } + } + } + + private ConnectorAttribute CreateTestAttribute() + { + return ConnectorAttributeBuilder.Build("foo", " bar "); + } + + private ConnectorAttribute CreateNormalizedTestAttribute() + { + return ConnectorAttributeBuilder.Build("foo", "bar"); + } + + private ObjectNormalizerFacade CreateTestNormalizer() + { + ObjectNormalizerFacade facade = new + ObjectNormalizerFacade(ObjectClass.ACCOUNT, + new MyAttributeNormalizer()); + return facade; + } + + private void AssertNormalizedFilter(Filter expectedNormalizedFilter, + Filter filter) + { + ObjectNormalizerFacade facade = + CreateTestNormalizer(); + filter = facade.NormalizeFilter(filter); + String expectedXml = SerializerUtil.SerializeXmlObject(expectedNormalizedFilter, false); + String actualXml = SerializerUtil.SerializeXmlObject(filter, false); + Assert.AreEqual(expectedXml, actualXml); + } + + + + [Test] + public void TestEndsWith() + { + Filter expected = + FilterBuilder.EndsWith(CreateNormalizedTestAttribute()); + Filter filter = + FilterBuilder.EndsWith(CreateTestAttribute()); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestStartsWith() + { + Filter expected = + FilterBuilder.StartsWith(CreateNormalizedTestAttribute()); + Filter filter = + FilterBuilder.StartsWith(CreateTestAttribute()); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestContains() + { + Filter expected = + FilterBuilder.Contains(CreateNormalizedTestAttribute()); + Filter filter = + FilterBuilder.Contains(CreateTestAttribute()); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestEqualTo() + { + Filter expected = + FilterBuilder.EqualTo(CreateNormalizedTestAttribute()); + Filter filter = + FilterBuilder.EqualTo(CreateTestAttribute()); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestGreaterThanOrEqualTo() + { + Filter expected = + FilterBuilder.GreaterThanOrEqualTo(CreateNormalizedTestAttribute()); + Filter filter = + FilterBuilder.GreaterThanOrEqualTo(CreateTestAttribute()); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestLessThanOrEqualTo() + { + Filter expected = + FilterBuilder.LessThanOrEqualTo(CreateNormalizedTestAttribute()); + Filter filter = + FilterBuilder.LessThanOrEqualTo(CreateTestAttribute()); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestLessThan() + { + Filter expected = + FilterBuilder.LessThan(CreateNormalizedTestAttribute()); + Filter filter = + FilterBuilder.LessThan(CreateTestAttribute()); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestGreaterThan() + { + Filter expected = + FilterBuilder.GreaterThan(CreateNormalizedTestAttribute()); + Filter filter = + FilterBuilder.GreaterThan(CreateTestAttribute()); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestAnd() + { + Filter expected = + FilterBuilder.And(FilterBuilder.Contains(CreateNormalizedTestAttribute()), + FilterBuilder.Contains(CreateNormalizedTestAttribute())); + Filter filter = + FilterBuilder.And(FilterBuilder.Contains(CreateTestAttribute()), + FilterBuilder.Contains(CreateTestAttribute())); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestOr() + { + Filter expected = + FilterBuilder.Or(FilterBuilder.Contains(CreateNormalizedTestAttribute()), + FilterBuilder.Contains(CreateNormalizedTestAttribute())); + Filter filter = + FilterBuilder.Or(FilterBuilder.Contains(CreateTestAttribute()), + FilterBuilder.Contains(CreateTestAttribute())); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestNot() + { + Filter expected = + FilterBuilder.Not(FilterBuilder.Contains(CreateNormalizedTestAttribute())); + Filter filter = + FilterBuilder.Not(FilterBuilder.Contains(CreateTestAttribute())); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestContainsAllValues() + { + Filter expected = + FilterBuilder.ContainsAllValues(CreateNormalizedTestAttribute()); + Filter filter = + FilterBuilder.ContainsAllValues(CreateTestAttribute()); + AssertNormalizedFilter(expected, filter); + } + [Test] + public void TestConnectorObject() + { + ConnectorObjectBuilder builder = + new ConnectorObjectBuilder(); + builder.SetName("myname"); + builder.SetUid("myuid"); + builder.AddAttribute(CreateTestAttribute()); + ConnectorObject v1 = builder.Build(); + ConnectorObject v2 = CreateTestNormalizer().NormalizeObject(v1); + builder = + new ConnectorObjectBuilder(); + builder.SetName("myname"); + builder.SetUid("myuid"); + builder.AddAttribute(CreateNormalizedTestAttribute()); + ConnectorObject expected = builder.Build(); + Assert.AreEqual(expected, v2); + Assert.IsFalse(expected.Equals(v1)); + } + + [Test] + public void TestSyncDelta() + { + ConnectorObjectBuilder objbuilder = + new ConnectorObjectBuilder(); + objbuilder.SetName("myname"); + objbuilder.SetUid("myuid"); + objbuilder.AddAttribute(CreateTestAttribute()); + ConnectorObject obj = objbuilder.Build(); + + SyncDeltaBuilder builder = + new SyncDeltaBuilder(); + builder.DeltaType = (SyncDeltaType.DELETE); + builder.Token = (new SyncToken("mytoken")); + builder.Uid = (new Uid("myuid")); + builder.Object = (obj); + SyncDelta v1 = builder.Build(); + SyncDelta v2 = CreateTestNormalizer().NormalizeSyncDelta(v1); + builder = + new SyncDeltaBuilder(); + builder.DeltaType = (SyncDeltaType.DELETE); + builder.Token = (new SyncToken("mytoken")); + builder.Uid = (new Uid("myuid")); + objbuilder = + new ConnectorObjectBuilder(); + objbuilder.SetName("myname"); + objbuilder.SetUid("myuid"); + objbuilder.AddAttribute(CreateNormalizedTestAttribute()); + builder.Object = objbuilder.Build(); + SyncDelta expected = builder.Build(); + Assert.AreEqual(expected, v2); + Assert.IsFalse(expected.Equals(v1)); + + } + + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/ObjectPoolTests.cs b/dotnet/framework/FrameworkTests/ObjectPoolTests.cs new file mode 100644 index 00000000..957c1404 --- /dev/null +++ b/dotnet/framework/FrameworkTests/ObjectPoolTests.cs @@ -0,0 +1,291 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Threading; +using NUnit.Framework; + +using Org.IdentityConnectors.Common.Pooling; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Impl.Api.Local; + +namespace FrameworkTests +{ + [TestFixture] + public class ObjectPoolTests + { + private class MyTestConnection + { + private bool _isGood = true; + + public void Test() + { + if (!_isGood) + { + throw new ConnectorException("Connection is bad"); + } + } + + public void Dispose() + { + _isGood = false; + } + + public bool IsGood + { + get + { + return _isGood; + } + } + } + + private class MyTestConnectionFactory : ObjectPoolHandler + { + + private bool _createBadConnection = false; + private int _totalCreatedConnections = 0; + + public MyTestConnection MakeObject() + { + _totalCreatedConnections++; + MyTestConnection rv = new MyTestConnection(); + if (_createBadConnection) + { + rv.Dispose(); + } + return rv; + } + public void TestObject(MyTestConnection obj) + { + obj.Test(); + } + public void DisposeObject(MyTestConnection obj) + { + obj.Dispose(); + } + + public int TotalCreatedConnections + { + get + { + return _totalCreatedConnections; + } + } + + public bool CreateBadConnection + { + set + { + _createBadConnection = value; + } + } + + public ObjectPoolConfiguration Validate(ObjectPoolConfiguration original) + { + return original; + } + + public void Shutdown() + { + } + } + + private class MyTestThread + { + private readonly ObjectPool _pool; + private readonly int _numIterations; + private Exception _exception; + private Thread _thisThread; + public MyTestThread(ObjectPool pool, + int numIterations) + { + _pool = pool; + _numIterations = numIterations; + _thisThread = new Thread(Run); + } + + public void Start() + { + _thisThread.Start(); + } + + public void Run() + { + try + { + for (int i = 0; i < _numIterations; i++) + { + MyTestConnection con = + _pool.BorrowObject(); + Thread.Sleep(300); + _pool.ReturnObject(con); + } + } + catch (Exception e) + { + _exception = e; + } + } + public void Shutdown() + { + _thisThread.Join(); + if (_exception != null) + { + throw _exception; + } + } + + } + + [Test] + public void TestWithManyThreads() + { + int NUM_ITERATIONS = 10; + int NUM_THREADS = 10; + int MAX_CONNECTIONS = NUM_THREADS - 3; //make sure we get some waiting + ObjectPoolConfiguration config = new ObjectPoolConfiguration(); + config.MaxObjects = (MAX_CONNECTIONS); + config.MaxIdle = (MAX_CONNECTIONS); + config.MinIdle = (MAX_CONNECTIONS); + config.MinEvictableIdleTimeMillis = (60 * 1000); + config.MaxWait = (60 * 1000); + MyTestConnectionFactory fact = new MyTestConnectionFactory(); + + ObjectPool pool = new ObjectPool(fact, config); + + MyTestThread[] threads = new MyTestThread[NUM_THREADS]; + for (int i = 0; i < threads.Length; i++) + { + threads[i] = new MyTestThread(pool, NUM_ITERATIONS); + threads[i].Start(); + } + + foreach (MyTestThread thread in threads) + { + thread.Shutdown(); + } + + //these should be the same since we never + //should have disposed anything + Assert.AreEqual(MAX_CONNECTIONS, fact.TotalCreatedConnections); + ObjectPool.Statistics stats = pool.GetStatistics(); + Assert.AreEqual(0, stats.NumActive); + Assert.AreEqual(MAX_CONNECTIONS, stats.NumIdle); + + pool.Shutdown(); + stats = pool.GetStatistics(); + Assert.AreEqual(0, stats.NumActive); + Assert.AreEqual(0, stats.NumIdle); + + } + + [Test] + public void TestBadConnection() + { + int MAX_CONNECTIONS = 3; + ObjectPoolConfiguration config = new ObjectPoolConfiguration(); + config.MaxObjects = (MAX_CONNECTIONS); + config.MaxIdle = (MAX_CONNECTIONS); + config.MinIdle = (MAX_CONNECTIONS); + config.MinEvictableIdleTimeMillis = (60 * 1000); + config.MaxWait = (60 * 1000); + MyTestConnectionFactory fact = new MyTestConnectionFactory(); + + ObjectPool pool = new ObjectPool(fact, config); + + //borrow first connection and return + MyTestConnection conn = pool.BorrowObject(); + Assert.AreEqual(1, fact.TotalCreatedConnections); + pool.ReturnObject(conn); + Assert.AreEqual(1, fact.TotalCreatedConnections); + + //re-borrow same connection and return + conn = pool.BorrowObject(); + Assert.AreEqual(1, fact.TotalCreatedConnections); + pool.ReturnObject(conn); + Assert.AreEqual(1, fact.TotalCreatedConnections); + + //dispose and make sure we get a new connection + conn.Dispose(); + conn = pool.BorrowObject(); + Assert.AreEqual(2, fact.TotalCreatedConnections); + pool.ReturnObject(conn); + Assert.AreEqual(2, fact.TotalCreatedConnections); + } + + [Test] + public void TestIdleCleanup() + { + ObjectPoolConfiguration config = new ObjectPoolConfiguration(); + config.MaxObjects = (3); + config.MaxIdle = (2); + config.MinIdle = (1); + config.MinEvictableIdleTimeMillis = (3000); + config.MaxWait = (60 * 1000); + MyTestConnectionFactory fact = new MyTestConnectionFactory(); + + ObjectPool pool = new ObjectPool(fact, config); + + MyTestConnection conn1 = (MyTestConnection)pool.BorrowObject(); + MyTestConnection conn2 = (MyTestConnection)pool.BorrowObject(); + MyTestConnection conn3 = (MyTestConnection)pool.BorrowObject(); + + Assert.AreEqual(3, fact.TotalCreatedConnections); + pool.ReturnObject(conn1); + Assert.AreEqual(1, pool.GetStatistics().NumIdle); + pool.ReturnObject(conn2); + Assert.AreEqual(2, pool.GetStatistics().NumIdle); + pool.ReturnObject(conn3); + Assert.AreEqual(2, pool.GetStatistics().NumIdle); + Assert.AreEqual(false, conn1.IsGood); + Assert.AreEqual(true, conn2.IsGood); + Assert.AreEqual(true, conn3.IsGood); + Thread.Sleep(((int)(config.MinEvictableIdleTimeMillis + 1000))); + MyTestConnection conn4 = (MyTestConnection)pool.BorrowObject(); + Assert.AreSame(conn3, conn4); + Assert.AreEqual(false, conn1.IsGood); + Assert.AreEqual(false, conn2.IsGood); + Assert.AreEqual(true, conn3.IsGood); + Assert.AreEqual(true, conn4.IsGood); + } + + [Test] + public void TestCreateBadConnection() + { + MyTestConnectionFactory fact = new MyTestConnectionFactory(); + fact.CreateBadConnection = (true); + + ObjectPool pool = new ObjectPool(fact, new ObjectPoolConfiguration()); + try + { + pool.BorrowObject(); + Assert.Fail("expected exception"); + } + catch (ConnectorException e) + { + Assert.AreEqual("Connection is bad", e.Message); + } + } + } +} diff --git a/dotnet/framework/FrameworkTests/ObjectSerializationTests.cs b/dotnet/framework/FrameworkTests/ObjectSerializationTests.cs new file mode 100755 index 00000000..2e0a4a4f --- /dev/null +++ b/dotnet/framework/FrameworkTests/ObjectSerializationTests.cs @@ -0,0 +1,1313 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ +using System; +using System.IO; +using System.Text; +using NUnit.Framework; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Pooling; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Common.Serializer; +using Org.IdentityConnectors.Framework.Impl.Api; +using Org.IdentityConnectors.Framework.Impl.Api.Remote; +using Org.IdentityConnectors.Framework.Impl.Api.Remote.Messages; + +namespace FrameworkTests +{ + [TestFixture] + public class ObjectSerializationTests + { + [Test] + public void TestBoolean() + { + bool v1 = true; + bool v2 = (bool)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = false; + v2 = (bool)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + [Test] + public void TestCharacter() + { + char v1 = 'h'; + char v2 = (char)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + [Test] + public void TestInteger() + { + int v1 = 12345; + int v2 = (int)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Int32.MinValue; + v2 = (int)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Int32.MaxValue; + v2 = (int)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = -1; + v2 = (int)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestLong() + { + long v1 = 12345; + long v2 = (long)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Int64.MinValue; + v2 = (long)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Int64.MaxValue; + v2 = (long)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = -1; + v2 = (long)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestFloat() + { + float v1 = 1.1F; + float v2 = (float)CloneObject(v1); + Assert.IsTrue(!Object.ReferenceEquals(v1, v2)); + Assert.AreEqual(v1, v2); + + v1 = Single.Epsilon; + v2 = (float)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Single.NaN; + v2 = (float)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Single.NegativeInfinity; + v2 = (float)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Single.PositiveInfinity; + v2 = (float)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Single.MinValue; + v2 = (float)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Single.MaxValue; + v2 = (float)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestDouble() + { + double v1 = 1.1; + double v2 = (double)CloneObject(v1); + Assert.IsTrue(!Object.ReferenceEquals(v1, v2)); + Assert.AreEqual(v1, v2); + + v1 = Double.Epsilon; + v2 = (double)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Double.NaN; + v2 = (double)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Double.NegativeInfinity; + v2 = (double)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Double.PositiveInfinity; + v2 = (double)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Double.MaxValue; + v2 = (double)CloneObject(v1); + Assert.AreEqual(v1, v2); + + v1 = Double.MinValue; + v2 = (double)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestString() + { + string v1 = "abcd"; + string v2 = (string)CloneObject(v1); + Assert.IsTrue(!Object.ReferenceEquals(v1, v2)); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestURI() + { + Uri v1 = new Uri("mailto:java-net@java.sun.com"); + Uri v2 = (Uri)CloneObject(v1); + Assert.IsTrue(!Object.ReferenceEquals(v1, v2)); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestFile() + { + FileName v1 = new FileName("c:/foo.txt"); + FileName v2 = (FileName)CloneObject(v1); + Assert.IsTrue(!Object.ReferenceEquals(v1, v2)); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestBigInteger() + { + BigInteger v1 = new BigInteger("983423442347324324324"); + BigInteger v2 = (BigInteger)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestBigDecimal() + { + BigDecimal v1 = new BigDecimal(new BigInteger("9847324324324"), 45); + BigDecimal v2 = (BigDecimal)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestByteArray() + { + byte[] v1 = { 1, 2, 3 }; + byte[] v2 = (byte[])CloneObject(v1); + Assert.AreEqual(3, v2.Length); + Assert.AreEqual(1, v2[0]); + Assert.AreEqual(2, v2[1]); + Assert.AreEqual(3, v2[2]); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestByte() + { + byte v1 = 51; + byte v2 = (byte)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestClasses() + { + Assert.AreEqual(typeof(bool), + CloneObject(typeof(bool))); + Assert.AreEqual(typeof(bool?), + CloneObject(typeof(bool?))); + Assert.AreEqual(typeof(bool[][]), + CloneObject(typeof(bool[][]))); + Assert.AreEqual(typeof(bool?[][]), + CloneObject(typeof(bool?[][]))); + Assert.AreEqual(typeof(char), + CloneObject(typeof(char))); + Assert.AreEqual(typeof(char?), + CloneObject(typeof(char?))); + //if this fails, we have added a new type and we need to add + //a serializer for it + Assert.IsTrue( + CollectionUtil.SetsEqual( + CollectionUtil.NewSet(FrameworkUtil.GetAllSupportedConfigTypes()), + (ICollection)CloneObject(FrameworkUtil.GetAllSupportedConfigTypes()))); + //if this fails, we have added a new type and we need to add + //a serializer for it + Assert.IsTrue( + CollectionUtil.SetsEqual( + CollectionUtil.NewSet(FrameworkUtil.GetAllSupportedAttributeTypes()), + (ICollection)CloneObject(FrameworkUtil.GetAllSupportedAttributeTypes()))); + ICollection apiOperations = + new HashSet(); + foreach (SafeType op in FrameworkUtil.AllAPIOperations()) + { + apiOperations.Add(op.RawType); + } + //if this fails, need to add to the OperationMappings class + Assert.IsTrue( + CollectionUtil.SetsEqual( + CollectionUtil.NewSet(apiOperations), + (ICollection)CloneObject(apiOperations))); + + } + + [Test] + public void TestArrays() + { + int[] v1 = { 1, 2, 3 }; + int[] v2 = (int[])CloneObject(v1); + Assert.AreEqual(3, v2.Length); + Assert.AreEqual(1, v2[0]); + Assert.AreEqual(2, v2[1]); + Assert.AreEqual(3, v2[2]); + } + + + [Test] + public void TestObjectArrays() + { + object[] v1 = { "1", "2", "3" }; + object[] v2 = (object[])CloneObject(v1); + Assert.AreEqual(3, v2.Length); + Assert.AreEqual("1", v2[0]); + Assert.AreEqual("2", v2[1]); + Assert.AreEqual("3", v2[2]); + } + + [Test] + public void TestDictionary() + { + IDictionary map = new Dictionary(); + map["key1"] = "val1"; + map["key2"] = "val2"; + IDictionary map2 = (IDictionary)CloneObject(map); + Assert.AreEqual("val1", map["key1"]); + Assert.AreEqual("val2", map["key2"]); + + IDictionary map3 = new Dictionary(); + map3["key1"] = "val1"; + map3["key2"] = "val2"; + IDictionary map4 = (IDictionary)CloneObject(map3); + Assert.AreEqual("val1", map4["key1"]); + Assert.AreEqual("val2", map4["key2"]); + } + [Test] + public void TestCaseInsensitiveMap() + { + HashSet set = new HashSet(); + set.Add(ConnectorAttributeBuilder.Build("foo1")); + set.Add(ConnectorAttributeBuilder.Build("foo2")); + IDictionary map = ConnectorAttributeUtil.ToMap(set); + Assert.IsTrue(map.ContainsKey("Foo1")); + Assert.IsTrue(map.ContainsKey("Foo2")); + IDictionary map2 = (IDictionary)CloneObject(map); + Assert.IsTrue(map2.ContainsKey("Foo1")); + Assert.IsTrue(map2.ContainsKey("Foo2")); + } + + [Test] + public void TestList() + { + IList v1 = new List(); + v1.Add("val1"); + v1.Add("val2"); + IList v2 = (IList)CloneObject(v1); + Assert.AreEqual(2, v2.Count); + Assert.AreEqual("val1", v2[0]); + Assert.AreEqual("val2", v2[1]); + + IList v3 = new List(); + v3.Add("val1"); + v3.Add("val2"); + + IList v4 = (IList)CloneObject(v3); + Assert.AreEqual(2, v4.Count); + Assert.AreEqual("val1", v4[0]); + Assert.AreEqual("val2", v4[1]); + } + + [Test] + public void TestSet() + { + //underneath the covers, this creates an + //ICollection - we need to test that ICollection + //becomes a Set + ICollection v1 = + CollectionUtil.NewReadOnlySet("val1", "val2"); + HashSet v2 = (HashSet)CloneObject(v1); + Assert.AreEqual(2, v2.Count); + Assert.IsTrue(v2.Contains("val1")); + Assert.IsTrue(v2.Contains("val2")); + + HashSet v3 = new HashSet(); + v3.Add("val1"); + v3.Add("val2"); + + HashSet v4 = (HashSet)CloneObject(v3); + Assert.AreEqual(2, v4.Count); + Assert.IsTrue(v4.Contains("val1")); + Assert.IsTrue(v4.Contains("val2")); + } + + [Test] + public void TestCaseInsensitiveSet() + { + ICollection v1 = CollectionUtil.NewCaseInsensitiveSet(); + v1.Add("foo"); + v1.Add("foo2"); + ICollection v2 = (ICollection)CloneObject(v1); + Assert.IsTrue(v2.Contains("Foo")); + Assert.IsTrue(v2.Contains("Foo2")); + } + + [Test] + public void TestCultureInfo() + { + //use this one since it uses all 3 fields of locale + CultureInfo v1 = new CultureInfo("nn-NO"); + CultureInfo v2 = (CultureInfo)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestObjectPoolConfiguration() + { + ObjectPoolConfiguration v1 = new ObjectPoolConfiguration(); + v1.MaxObjects = 1; + v1.MaxIdle = 2; + v1.MaxWait = 3; + v1.MinEvictableIdleTimeMillis = 4; + v1.MinIdle = 5; + ObjectPoolConfiguration v2 = + (ObjectPoolConfiguration)CloneObject(v1); + Assert.IsTrue(!Object.ReferenceEquals(v1, v2)); + + Assert.AreEqual(v1, v2); + + Assert.AreEqual(1, v2.MaxObjects); + Assert.AreEqual(2, v2.MaxIdle); + Assert.AreEqual(3, v2.MaxWait); + Assert.AreEqual(4, v2.MinEvictableIdleTimeMillis); + Assert.AreEqual(5, v2.MinIdle); + } + [Test] + public void TestConfigurationProperty() + { + ConfigurationPropertyImpl v1 = new ConfigurationPropertyImpl(); + v1.Order = (1); + v1.IsConfidential = (true); + v1.IsRequired = true; + v1.Name = ("foo"); + v1.HelpMessageKey = ("help key"); + v1.DisplayMessageKey = ("display key"); + v1.GroupMessageKey = ("group key"); + v1.Value = ("bar"); + v1.ValueType = (typeof(string)); + v1.Operations = FrameworkUtil.AllAPIOperations(); + + ConfigurationPropertyImpl v2 = (ConfigurationPropertyImpl) + CloneObject(v1); + Assert.AreEqual(1, v2.Order); + Assert.IsTrue(v2.IsConfidential); + Assert.IsTrue(v2.IsRequired); + Assert.AreEqual("foo", v2.Name); + Assert.AreEqual("help key", v2.HelpMessageKey); + Assert.AreEqual("display key", v2.DisplayMessageKey); + Assert.AreEqual("group key", v2.GroupMessageKey); + Assert.AreEqual("bar", v2.Value); + Assert.AreEqual(typeof(string), v2.ValueType); + Assert.IsTrue(CollectionUtil.Equals( + FrameworkUtil.AllAPIOperations(), v2.Operations)); + } + + [Test] + public void TestConfigurationProperties() + { + ConfigurationPropertyImpl prop1 = new ConfigurationPropertyImpl(); + prop1.Order = (1); + prop1.IsConfidential = (true); + prop1.Name = ("foo"); + prop1.HelpMessageKey = ("help key"); + prop1.DisplayMessageKey = ("display key"); + prop1.GroupMessageKey = ("group key"); + prop1.Value = ("bar"); + prop1.ValueType = (typeof(string)); + prop1.Operations = null; + + ConfigurationPropertiesImpl v1 = new ConfigurationPropertiesImpl(); + v1.Properties = (CollectionUtil.NewReadOnlyList(prop1)); + v1.SetPropertyValue("foo", "bar"); + + ConfigurationPropertiesImpl v2 = (ConfigurationPropertiesImpl) + CloneObject(v1); + Assert.AreEqual("bar", v2.GetProperty("foo").Value); + } + + [Test] + public void TestAPIConfiguration() + { + ConfigurationPropertyImpl prop1 = new ConfigurationPropertyImpl(); + prop1.Order = (1); + prop1.IsConfidential = (true); + prop1.Name = ("foo"); + prop1.HelpMessageKey = ("help key"); + prop1.DisplayMessageKey = ("display key"); + prop1.GroupMessageKey = ("group key"); + prop1.Value = ("bar"); + prop1.ValueType = (typeof(string)); + prop1.Operations = null; + + ConfigurationPropertiesImpl props1 = new ConfigurationPropertiesImpl(); + props1.Properties = (CollectionUtil.NewReadOnlyList(prop1)); + + APIConfigurationImpl v1 = new APIConfigurationImpl(); + v1.ConnectorPoolConfiguration = (new ObjectPoolConfiguration()); + v1.ConfigurationProperties = (props1); + v1.IsConnectorPoolingSupported = (true); + v1.ProducerBufferSize = (200); + v1.SupportedOperations = (FrameworkUtil.AllAPIOperations()); + IDictionary, int> map = + CollectionUtil.NewDictionary, int>(SafeType.Get(), 6); + v1.TimeoutMap = (map); + + APIConfigurationImpl v2 = (APIConfigurationImpl) + CloneObject(v1); + Assert.IsTrue(!Object.ReferenceEquals(v1, v2)); + Assert.IsNotNull(v2.ConnectorPoolConfiguration); + Assert.IsNotNull(v2.ConfigurationProperties); + Assert.AreEqual(v1.ConnectorPoolConfiguration, v2.ConnectorPoolConfiguration); + Assert.AreEqual(v1.ConfigurationProperties, v2.ConfigurationProperties); + Assert.IsTrue(v2.IsConnectorPoolingSupported); + Assert.AreEqual(200, v2.ProducerBufferSize); + Assert.IsTrue(CollectionUtil.SetsEqual( + FrameworkUtil.AllAPIOperations(), + v2.SupportedOperations)); + Assert.AreEqual(map, v2.TimeoutMap); + } + + [Test] + public void TestConnectorMessages() + { + ConnectorMessagesImpl v1 = new ConnectorMessagesImpl(); + IDictionary defaultMap = new Dictionary(); + defaultMap["key1"] = "val1"; + IDictionary> messages = + new Dictionary>(); + messages[new CultureInfo("en")] = defaultMap; + messages[new CultureInfo("")] = defaultMap; + v1.Catalogs = (messages); + + ConnectorMessagesImpl v2 = (ConnectorMessagesImpl) + CloneObject(v1); + Assert.IsTrue( + CollectionUtil.SetsEqual(messages[new CultureInfo("")], + v2.Catalogs[new CultureInfo("")])); + Assert.IsTrue( + CollectionUtil.SetsEqual(messages[new CultureInfo("en")], + v2.Catalogs[new CultureInfo("en")])); + } + + [Test] + public void TestRemoteConnectorInfo() + { + RemoteConnectorInfoImpl v1 = new RemoteConnectorInfoImpl(); + v1.Messages = (new ConnectorMessagesImpl()); + v1.ConnectorKey = (new ConnectorKey("my bundle", + "my version", + "my connector")); + ConfigurationPropertiesImpl configProperties = new ConfigurationPropertiesImpl(); + configProperties.Properties = (new List()); + APIConfigurationImpl apiImpl = new APIConfigurationImpl(); + apiImpl.ConfigurationProperties = (configProperties); + v1.DefaultAPIConfiguration = (apiImpl); + v1.ConnectorDisplayNameKey = ("mykey"); + v1.ConnectorCategoryKey = ("LDAP"); + + RemoteConnectorInfoImpl v2 = (RemoteConnectorInfoImpl) + CloneObject(v1); + + Assert.IsNotNull(v2.Messages); + Assert.AreEqual("my bundle", v2.ConnectorKey.BundleName); + Assert.AreEqual("my version", v2.ConnectorKey.BundleVersion); + Assert.AreEqual("my connector", v2.ConnectorKey.ConnectorName); + Assert.AreEqual("mykey", v2.ConnectorDisplayNameKey); + Assert.AreEqual("LDAP", v2.ConnectorCategoryKey); + Assert.IsNotNull(v2.DefaultAPIConfiguration); + } + + [Test] + public void TestAttribute() + { + ConnectorAttribute v1 = ConnectorAttributeBuilder.Build("foo", "val1", "val2"); + ConnectorAttribute v2 = (ConnectorAttribute)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestAttributeInfo() + { + + ConnectorAttributeInfoBuilder builder = new ConnectorAttributeInfoBuilder(); + builder.Name = ("foo"); + builder.ValueType = (typeof(String)); + builder.Required = (true); + builder.Readable = (true); + builder.Creatable = (true); + builder.Updateable = (true); + builder.MultiValued = (true); + builder.ReturnedByDefault = false; + ConnectorAttributeInfo v1 = builder.Build(); + ConnectorAttributeInfo v2 = (ConnectorAttributeInfo)CloneObject(v1); + Assert.AreEqual(v1, v2); + Assert.AreEqual("foo", v2.Name); + Assert.AreEqual(typeof(String), v2.ValueType); + Assert.IsTrue(v2.IsMultiValued); + Assert.IsTrue(v2.IsReadable); + Assert.IsTrue(v2.IsRequired); + Assert.IsTrue(v2.IsUpdateable); + Assert.IsTrue(v2.IsCreatable); + Assert.IsFalse(v2.IsReturnedByDefault); + + builder.InfoFlags = AllFlags(); + v1 = builder.Build(); + v2 = (ConnectorAttributeInfo)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + private ConnectorAttributeInfo.Flags AllFlags() + { + ConnectorAttributeInfo.Flags flags = + ConnectorAttributeInfo.Flags.NONE; + foreach (Enum e in Enum.GetValues(typeof(ConnectorAttributeInfo.Flags))) + { + ConnectorAttributeInfo.Flags f = + (ConnectorAttributeInfo.Flags)e; + flags |= f; + } + return flags; + } + + [Test] + public void TestConnectorObject() + { + ConnectorObjectBuilder bld = new ConnectorObjectBuilder(); + bld.SetUid("foo"); + bld.SetName("fooToo"); + + ConnectorObject v1 = bld.Build(); + ConnectorObject v2 = (ConnectorObject)CloneObject(v1); + + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestName() + { + Name v1 = new Name("Test"); + Name v2 = (Name)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestObjectClass() + { + ObjectClass v1 = new ObjectClass("test"); + ObjectClass v2 = (ObjectClass)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestObjectClassInfo() + { + ConnectorAttributeInfoBuilder builder = new ConnectorAttributeInfoBuilder(); + builder.Name = ("foo"); + builder.ValueType = (typeof(String)); + builder.Required = (true); + builder.Readable = (true); + builder.Updateable = (true); + builder.MultiValued = (true); + ObjectClassInfoBuilder obld = new ObjectClassInfoBuilder(); + obld.ObjectType = ObjectClass.ACCOUNT_NAME; + obld.IsContainer = true; + obld.AddAttributeInfo(builder.Build()); + ObjectClassInfo v1 = obld.Build(); + ObjectClassInfo v2 = (ObjectClassInfo)CloneObject(v1); + Assert.AreEqual(v1, v2); + Assert.IsTrue(v2.IsContainer); + } + + [Test] + public void TestUid() + { + Uid v1 = new Uid("test"); + Uid v2 = (Uid)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void testOperationOptionInfo() + { + OperationOptionInfo v1 = + new OperationOptionInfo("name", typeof(int?)); + OperationOptionInfo v2 = + (OperationOptionInfo)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestSchema() + { + OperationOptionInfo opInfo = + new OperationOptionInfo("name", typeof(int?)); + ObjectClassInfoBuilder bld = new ObjectClassInfoBuilder(); + bld.ObjectType = ObjectClass.ACCOUNT_NAME; + ObjectClassInfo info = bld.Build(); + ICollection temp = CollectionUtil.NewSet(info); + IDictionary, ICollection> map = + new Dictionary, ICollection>(); + map[SafeType.Get()] = temp; + ICollection temp2 = CollectionUtil.NewSet(opInfo); + IDictionary, ICollection> map2 = + new Dictionary, ICollection>(); + map2[SafeType.Get()] = temp2; + Schema v1 = new Schema(CollectionUtil.NewSet(info), + CollectionUtil.NewSet(opInfo), + map, + map2); + Schema v2 = (Schema)CloneObject(v1); + Assert.AreEqual(v1, v2); + Assert.AreEqual(info, v2.ObjectClassInfo.First()); + Assert.AreEqual(1, v2.SupportedObjectClassesByOperation.Count); + Assert.AreEqual(1, v2.SupportedOptionsByOperation.Count); + Assert.AreEqual(1, v2.OperationOptionInfo.Count); + } + + [Test] + public void TestContainsFilter() + { + ContainsFilter v1 = new ContainsFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + ContainsFilter v2 = (ContainsFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + } + + [Test] + public void TestAndFilter() + { + ContainsFilter left1 = new ContainsFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + ContainsFilter right1 = new ContainsFilter(ConnectorAttributeBuilder.Build("foo2", "bar2")); + AndFilter v1 = new AndFilter(left1, right1); + AndFilter v2 = (AndFilter)CloneObject(v1); + ContainsFilter left2 = (ContainsFilter)v2.Left; + ContainsFilter right2 = (ContainsFilter)v2.Right; + Assert.AreEqual(left1.GetAttribute(), left2.GetAttribute()); + Assert.AreEqual(right1.GetAttribute(), right2.GetAttribute()); + } + + [Test] + public void TestEndsWithFilter() + { + EndsWithFilter v1 = new EndsWithFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + EndsWithFilter v2 = (EndsWithFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + } + + [Test] + public void TestEqualsFilter() + { + EqualsFilter v1 = new EqualsFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + EqualsFilter v2 = (EqualsFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + } + + [Test] + public void TestGreaterThanFilter() + { + GreaterThanFilter v1 = new GreaterThanFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + GreaterThanFilter v2 = (GreaterThanFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + } + + [Test] + public void TestGreaterThanOrEqualFilter() + { + GreaterThanOrEqualFilter v1 = new GreaterThanOrEqualFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + GreaterThanOrEqualFilter v2 = (GreaterThanOrEqualFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + } + + [Test] + public void TestLessThanFilter() + { + LessThanFilter v1 = new LessThanFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + LessThanFilter v2 = (LessThanFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + } + + [Test] + public void TestLessThanOrEqualFilter() + { + LessThanOrEqualFilter v1 = new LessThanOrEqualFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + LessThanOrEqualFilter v2 = (LessThanOrEqualFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + } + + [Test] + public void TestNotFilter() + { + ContainsFilter left1 = new ContainsFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + NotFilter v1 = new NotFilter(left1); + NotFilter v2 = (NotFilter)CloneObject(v1); + ContainsFilter left2 = (ContainsFilter)v2.Filter; + Assert.AreEqual(left1.GetAttribute(), left2.GetAttribute()); + } + + [Test] + public void TestOrFilter() + { + ContainsFilter left1 = new ContainsFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + ContainsFilter right1 = new ContainsFilter(ConnectorAttributeBuilder.Build("foo2", "bar2")); + OrFilter v1 = new OrFilter(left1, right1); + OrFilter v2 = (OrFilter)CloneObject(v1); + ContainsFilter left2 = (ContainsFilter)v2.Left; + ContainsFilter right2 = (ContainsFilter)v2.Right; + Assert.AreEqual(left1.GetAttribute(), left2.GetAttribute()); + Assert.AreEqual(right1.GetAttribute(), right2.GetAttribute()); + } + + [Test] + public void TestStartsWithFilter() + { + StartsWithFilter v1 = new StartsWithFilter(ConnectorAttributeBuilder.Build("foo", "bar")); + StartsWithFilter v2 = (StartsWithFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + } + + [Test] + public void TestContainsAllValuesFilter() + { + ContainsAllValuesFilter v1 = new ContainsAllValuesFilter(ConnectorAttributeBuilder.Build("foo", "bar", "foo")); + ContainsAllValuesFilter v2 = (ContainsAllValuesFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + } + + + [Test] + public void TestPresenceFilter() + { + PresenceFilter v1 = new PresenceFilter("foo"); + PresenceFilter v2 = (PresenceFilter)CloneObject(v1); + Assert.AreEqual(v1.Name, v2.Name); + } + + [Test] + public void TestExtendedMatchFilter() + { + ExtendedMatchFilter v1 = new ExtendedMatchFilter("bar", ConnectorAttributeBuilder.Build("foo", "a", "b")); + ExtendedMatchFilter v2 = (ExtendedMatchFilter)CloneObject(v1); + Assert.AreEqual(v1.GetAttribute(), v2.GetAttribute()); + Assert.AreEqual(v1.Operator, v2.Operator); + } + + + [Test] + public void TestExceptions() + { + { + AlreadyExistsException v1 = new AlreadyExistsException("ex"); + AlreadyExistsException v2 = (AlreadyExistsException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + ConfigurationException v1 = new ConfigurationException("ex"); + ConfigurationException v2 = (ConfigurationException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + ConnectionBrokenException v1 = new ConnectionBrokenException("ex"); + ConnectionBrokenException v2 = (ConnectionBrokenException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + ConnectionFailedException v1 = new ConnectionFailedException("ex"); + ConnectionFailedException v2 = (ConnectionFailedException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + ConnectorException v1 = new ConnectorException("ex"); + ConnectorException v2 = (ConnectorException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + { + ConnectorIOException v1 = new ConnectorIOException("ex"); + ConnectorIOException v2 = (ConnectorIOException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + { + ConnectorSecurityException v1 = new ConnectorSecurityException("ex"); + ConnectorSecurityException v2 = (ConnectorSecurityException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + InvalidCredentialException v1 = new InvalidCredentialException("ex"); + InvalidCredentialException v2 = (InvalidCredentialException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + InvalidPasswordException v1 = new InvalidPasswordException("ex"); + InvalidPasswordException v2 = (InvalidPasswordException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + PasswordExpiredException v1 = new PasswordExpiredException("ex"); + v1.Uid = (new Uid("myuid")); + PasswordExpiredException v2 = (PasswordExpiredException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + Assert.AreEqual("myuid", v2.Uid.GetUidValue()); + } + + { + OperationTimeoutException v1 = new OperationTimeoutException("ex"); + OperationTimeoutException v2 = (OperationTimeoutException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + PermissionDeniedException v1 = new PermissionDeniedException("ex"); + PermissionDeniedException v2 = (PermissionDeniedException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + UnknownUidException v1 = new UnknownUidException("ex"); + UnknownUidException v2 = (UnknownUidException)CloneObject(v1); + Assert.AreEqual("ex", v2.Message); + } + + { + ArgumentException v1 = new ArgumentException("my msg"); + ArgumentException v2 = (ArgumentException)CloneObject(v1); + Assert.AreEqual("my msg", v2.Message); + } + + { + ArgumentNullException v1 = new ArgumentNullException(null, "my msg 1"); + ArgumentException v2 = (ArgumentException)CloneObject(v1); + Assert.AreEqual("my msg 1", v2.Message); + } + + { + Exception v1 = new Exception("my msg2"); + Exception v2 = (Exception)CloneObject(v1); + Assert.AreEqual("my msg2", v2.Message); + } + + } + + [Test] + public void TestHelloRequest() + { + HelloRequest v1 = new HelloRequest(HelloRequest.CONNECTOR_INFO); + HelloRequest v2 = (HelloRequest)CloneObject(v1); + Assert.IsNotNull(v2); + Assert.AreEqual(HelloRequest.CONNECTOR_INFO, v2.GetInfoLevel()); + } + + [Test] + public void TestHelloResponse() + { + Exception ex = new Exception("foo"); + IDictionary serverInfo = new Dictionary(1); + serverInfo.Add(HelloResponse.SERVER_START_TIME, DateTimeUtil.GetCurrentUtcTimeMillis()); + ConnectorKey key = new ConnectorKey("my bundle", "my version", "my connector"); + RemoteConnectorInfoImpl info = new RemoteConnectorInfoImpl(); + info.Messages = (new ConnectorMessagesImpl()); + info.ConnectorKey = (key); + ConfigurationPropertiesImpl configProperties = new ConfigurationPropertiesImpl(); + configProperties.Properties = (new List()); + APIConfigurationImpl apiImpl = new APIConfigurationImpl(); + apiImpl.ConfigurationProperties = (configProperties); + info.DefaultAPIConfiguration = (apiImpl); + info.ConnectorDisplayNameKey = ("mykey"); + info.ConnectorCategoryKey = (""); + + + HelloResponse v1 = new HelloResponse(ex, serverInfo, CollectionUtil.NewReadOnlyList(key), CollectionUtil.NewReadOnlyList(info)); + HelloResponse v2 = (HelloResponse)CloneObject(v1); + Assert.IsNotNull(v2.Exception); + Assert.IsNotNull(v2.ServerInfo[HelloResponse.SERVER_START_TIME]); + Assert.IsNotNull(v2.ConnectorKeys.First()); + Assert.IsNotNull(v2.ConnectorInfos.First()); + } + + [Test] + public void TestOperationRequest() + { + ConfigurationPropertiesImpl configProperties = new ConfigurationPropertiesImpl(); + configProperties.Properties = (new List()); + APIConfigurationImpl apiImpl = new APIConfigurationImpl(); + apiImpl.ConfigurationProperties = (configProperties); + + IList args = new List(); + args.Add("my arg"); + OperationRequest v1 = new + OperationRequest(new ConnectorKey("my bundle", + "my version", + "my connector"), + SerializerUtil.SerializeBase64Object(apiImpl), + SafeType.Get(), + "mymethodName", + args); + OperationRequest v2 = (OperationRequest)CloneObject(v1); + Assert.AreEqual("my bundle", v2.ConnectorKey.BundleName); + Assert.AreEqual("my version", v2.ConnectorKey.BundleVersion); + Assert.AreEqual("my connector", v2.ConnectorKey.ConnectorName); + Assert.IsNotNull(v2.ConnectorFacadeKey); + Assert.AreEqual(SafeType.Get(), v2.Operation); + Assert.AreEqual("mymethodName", v2.OperationMethodName); + Assert.IsTrue( + CollectionUtil.Equals( + args, v2.Arguments)); + } + + [Test] + public void TestOperationResponseEnd() + { + OperationResponseEnd v1 = new OperationResponseEnd(); + OperationResponseEnd v2 = (OperationResponseEnd)CloneObject(v1); + Assert.IsNotNull(v2); + } + [Test] + public void TestOperationResponsePart() + { + Exception ex = new Exception("foo", new ArgumentException("Cause")); + OperationResponsePart v1 = new OperationResponsePart(ex, "bar"); + OperationResponsePart v2 = (OperationResponsePart)CloneObject(v1); + Assert.IsNotNull(v2.Exception); + Assert.AreEqual("bar", v2.Result); + } + + [Test] + public void TestOperationResponsePause() + { + OperationResponsePause v1 = new OperationResponsePause(); + OperationResponsePause v2 = (OperationResponsePause)CloneObject(v1); + Assert.IsNotNull(v2); + } + + [Test] + public void TestOperationRequestMoreData() + { + OperationRequestMoreData v1 = new OperationRequestMoreData(); + OperationRequestMoreData v2 = (OperationRequestMoreData)CloneObject(v1); + Assert.IsNotNull(v2); + } + + [Test] + public void TestOperationRequestStopData() + { + OperationRequestStopData v1 = new OperationRequestStopData(); + OperationRequestStopData v2 = (OperationRequestStopData)CloneObject(v1); + Assert.IsNotNull(v2); + } + + [Test] + public void TestEchoMessage() + { + EchoMessage v1 = new EchoMessage("test", "xml"); + EchoMessage v2 = (EchoMessage)CloneObject(v1); + Assert.AreEqual("test", v2.Object); + Assert.AreEqual("xml", v2.ObjectXml); + } + + [Test] + public void TestOperationOptions() + { + OperationOptionsBuilder builder = new OperationOptionsBuilder(); + builder.SetOption("foo", "bar"); + builder.SetOption("foo2", "bar2"); + OperationOptions v1 = builder.Build(); + OperationOptions v2 = (OperationOptions)CloneObject(v1); + Assert.AreEqual(2, v2.Options.Count); + Assert.AreEqual("bar", v2.Options["foo"]); + Assert.AreEqual("bar2", v2.Options["foo2"]); + } + + [Test] + public void TestSearchResult() + { + SearchResult v1 = new SearchResult("FE00", SearchResult.CountPolicy.ESTIMATE, 100, 10); + SearchResult v2 = (SearchResult)CloneObject(v1); + Assert.AreEqual(v1.PagedResultsCookie, v2.PagedResultsCookie); + Assert.AreEqual(v1.TotalPagedResultsPolicy, v2.TotalPagedResultsPolicy); + Assert.AreEqual(v1.TotalPagedResults, v2.TotalPagedResults); + Assert.AreEqual(v1.RemainingPagedResults, v2.RemainingPagedResults); + } + + + [Test] + public void TestScript() + { + ScriptBuilder builder = new ScriptBuilder(); + builder.ScriptLanguage = "language"; + builder.ScriptText = "text"; + Script v1 = builder.Build(); + Script v2 = (Script)CloneObject(v1); + Assert.AreEqual("language", v2.ScriptLanguage); + Assert.AreEqual("text", v2.ScriptText); + } + + [Test] + public void TestScriptContext() + { + ScriptContextBuilder builder = new ScriptContextBuilder(); + builder.ScriptLanguage = ("language"); + builder.ScriptText = ("text"); + builder.AddScriptArgument("foo", "bar"); + builder.AddScriptArgument("foo2", "bar2"); + ScriptContext v1 = builder.Build(); + ScriptContext v2 = (ScriptContext)CloneObject(v1); + Assert.AreEqual(2, v2.ScriptArguments.Count); + Assert.AreEqual("bar", v2.ScriptArguments["foo"]); + Assert.AreEqual("bar2", v2.ScriptArguments["foo2"]); + Assert.AreEqual("language", v2.ScriptLanguage); + Assert.AreEqual("text", v2.ScriptText); + } + [Test] + public void TestSyncDeltaType() + { + SyncDeltaType v1 = SyncDeltaType.DELETE; + SyncDeltaType v2 = (SyncDeltaType)CloneObject(v1); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestSyncToken() + { + SyncToken v1 = new SyncToken("mytoken"); + SyncToken v2 = (SyncToken)CloneObject(v1); + Assert.AreEqual(v1.Value, v2.Value); + Assert.AreEqual(v1, v2); + } + + [Test] + public void TestSyncDelta() + { + ConnectorObjectBuilder bld = new ConnectorObjectBuilder(); + bld.SetUid("foo"); + bld.SetName("name"); + SyncDeltaBuilder builder = new SyncDeltaBuilder(); + builder.PreviousUid = (new Uid("mypreviousuid")); + builder.Uid = (new Uid("myuid")); + builder.DeltaType = (SyncDeltaType.CREATE_OR_UPDATE); + builder.Token = (new SyncToken("mytoken")); + builder.Object = (bld.Build()); + SyncDelta v1 = builder.Build(); + SyncDelta v2 = (SyncDelta)CloneObject(v1); + Assert.AreEqual(new Uid("mypreviousuid"), v2.PreviousUid); + Assert.AreEqual(new Uid("foo"), v2.Uid); + Assert.AreEqual(new SyncToken("mytoken"), v2.Token); + Assert.AreEqual(SyncDeltaType.CREATE_OR_UPDATE, v2.DeltaType); + Assert.AreEqual(v1, v2); + + builder = new SyncDeltaBuilder(); + builder.DeltaType = SyncDeltaType.DELETE; + builder.Token = new SyncToken("mytoken"); + builder.ObjectClass = ObjectClass.ACCOUNT; + builder.Uid = new Uid("foo"); + v1 = builder.Build(); + v2 = (SyncDelta)CloneObject(v1); + Assert.AreEqual(ObjectClass.ACCOUNT, v2.ObjectClass); + Assert.AreEqual(new Uid("foo"), v2.Uid); + Assert.AreEqual(new SyncToken("mytoken"), v2.Token); + Assert.AreEqual(SyncDeltaType.DELETE, v2.DeltaType); + Assert.AreEqual(v1, v2); + + + + } + + [Test] + public void TestNull() + { + Object v1 = null; + Object v2 = CloneObject(v1); + Assert.IsNull(v2); + } + + [Test] + public void TestGuardedByteArray() + { + GuardedByteArray v1 = new GuardedByteArray(); + v1.AppendByte(0x00); + v1.AppendByte(0x01); + v1.AppendByte(0x02); + GuardedByteArray v2 = (GuardedByteArray)CloneObject(v1); + Assert.AreEqual(new byte[] { 0x00, 0x01, 0x02 }, DecryptToByteArray(v2)); + } + + [Test] + public void TestGuardedString() + { + GuardedString v1 = new GuardedString(); + v1.AppendChar('f'); + v1.AppendChar('o'); + v1.AppendChar('o'); + v1.AppendChar('b'); + v1.AppendChar('a'); + v1.AppendChar('r'); + GuardedString v2 = (GuardedString)CloneObject(v1); + Assert.AreEqual("foobar", DecryptToString(v2)); + } + + [Test] + public void TestQualifiedUid() + { + QualifiedUid v1 = new QualifiedUid(new ObjectClass("myclass"), + new Uid("myuid")); + QualifiedUid v2 = (QualifiedUid)CloneObject(v1); + Assert.AreEqual(v1, v2); + Assert.AreEqual("myclass", v2.ObjectClass.GetObjectClassValue()); + Assert.AreEqual("myuid", v2.Uid.GetUidValue()); + } + + /// + /// Highly insecure method! Do not do this in production + /// code. + /// + /// + /// This is only for test purposes + /// + private String DecryptToString(GuardedString str) + { + StringBuilder buf = new StringBuilder(); + str.Access(new GuardedString.LambdaAccessor( + array => + { + for (int i = 0; i < array.Length; i++) + { + buf.Append(array[i]); + } + })); + return buf.ToString(); + } + + private byte[] DecryptToByteArray(GuardedByteArray bytes) + { + byte[] result = null; + bytes.Access(new GuardedByteArray.LambdaAccessor( + array => + { + result = new byte[array.Length]; + for (int i = 0; i < array.Length; i++) + { + result[i] = array[i]; + } + })); + return result; + } + + protected virtual Object CloneObject(Object o) + { + return SerializerUtil.CloneObject(o); + } + } + [TestFixture] + public class XmlSerializationTests : ObjectSerializationTests + { + protected override Object CloneObject(Object o) + { + String xml = SerializerUtil.SerializeXmlObject(o, true); + Console.WriteLine(xml); + o = SerializerUtil.DeserializeXmlObject(xml, true); + + //pass through a list to make sure dtd correctly defines all xml objects + List list = new List(); + list.Add(o); + xml = SerializerUtil.SerializeXmlObject(list, true); + Console.WriteLine(xml); + IList rv = (IList)SerializerUtil.DeserializeXmlObject(xml, true); + return rv[0]; + } + + [Test] + public void TestMultiObject() + { + ObjectSerializerFactory factory = ObjectSerializerFactory.GetInstance(); + StringWriter sw = new StringWriter(); + XmlObjectSerializer ser = factory.NewXmlSerializer(sw, true, true); + ser.WriteObject("foo"); + ser.WriteObject("bar"); + ser.Close(true); + String xml = sw.ToString(); + Console.WriteLine(xml); + IList results = new List(); + factory.DeserializeXmlStream(new StringReader(xml), + o => + { + results.Add(o); + return true; + }, + true); + Assert.AreEqual(2, results.Count); + Assert.AreEqual("foo", results[0]); + Assert.AreEqual("bar", results[1]); + } + + [Test] + public void TestWriter() + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.OmitXmlDeclaration = true; + XmlWriter writer = XmlWriter.Create(Console.Out, settings); + + // Write the book element. + writer.WriteStartElement("book"); + writer.WriteEndElement(); + writer.Close(); + // Write the title element. + //writer.WriteStartElement("title"); + //writer.WriteString("Pride And Prejudice<<"); + //writer.WriteEndElement(); + + // Write the close tag for the root element. + //writer.WriteEndElement(); + + // Write the XML and close the writer. + //writer.Close(); + + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/Org.IdentityConnectors.TestConnector.FakeConnector/config/config.xml b/dotnet/framework/FrameworkTests/Org.IdentityConnectors.TestConnector.FakeConnector/config/config.xml new file mode 100644 index 00000000..f080732c --- /dev/null +++ b/dotnet/framework/FrameworkTests/Org.IdentityConnectors.TestConnector.FakeConnector/config/config.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/Org.IdentityConnectors.TestConnector.FakeConnector/config/converter/config.xml b/dotnet/framework/FrameworkTests/Org.IdentityConnectors.TestConnector.FakeConnector/config/converter/config.xml new file mode 100755 index 00000000..889cce4f --- /dev/null +++ b/dotnet/framework/FrameworkTests/Org.IdentityConnectors.TestConnector.FakeConnector/config/converter/config.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/framework/FrameworkTests/Org.IdentityConnectors.TestConnector.FakeConnector/config/myconfig/config.xml b/dotnet/framework/FrameworkTests/Org.IdentityConnectors.TestConnector.FakeConnector/config/myconfig/config.xml new file mode 100644 index 00000000..b9c4303d --- /dev/null +++ b/dotnet/framework/FrameworkTests/Org.IdentityConnectors.TestConnector.FakeConnector/config/myconfig/config.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/PropertyBagTests.cs b/dotnet/framework/FrameworkTests/PropertyBagTests.cs new file mode 100644 index 00000000..05426ea5 --- /dev/null +++ b/dotnet/framework/FrameworkTests/PropertyBagTests.cs @@ -0,0 +1,134 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Org.IdentityConnectors.Test.Common; +using Org.IdentityConnectors.Common; + +namespace FrameworkTests +{ + [TestFixture] + public class PropertyBagTests + { + private PropertyBag bag = null; + + [SetUp] + public void SetUp() + { + bag = CreateBag(); + } + + [Test] + public void TestGetProperty() + { + Assert.AreEqual("value1", bag.GetProperty("key1")); + Assert.IsNull(bag.GetProperty("key2")); + + Assert.AreEqual((int?)1, bag.GetProperty("key3")); + Assert.AreEqual((long?)1, bag.GetProperty("key5")); + + // try not existing + try + { + bag.GetProperty("key4"); + Assert.Fail("Get Property must fail for unexisting property"); + } + catch (ArgumentException) + { + } + + // Try cast + try + { + bag.GetProperty("key3"); + Assert.Fail("Get Property with incompatible type must fail on InvalidCastException"); + } + catch (InvalidCastException) + { + } + } + + [Test] + public void TestGetPropertyWithDef() + { + Assert.AreEqual("value1", bag.GetProperty("key1", "def")); + Assert.IsNull(bag.GetProperty("key2", "def")); + Assert.AreEqual("def", bag.GetProperty("key4", "def")); + Assert.IsNull(bag.GetProperty("key4", null)); + } + + [Test] + public void TestGetStringProperty() + { + Assert.AreEqual("value1", bag.GetStringProperty("key1")); + Assert.IsNull(bag.GetStringProperty("key2")); + // Try cast + try + { + bag.GetStringProperty("key3"); + Assert.Fail("Get Property with incompatible type must fail on InvalidCastException"); + } + catch (InvalidCastException) + { + } + + // try not existing + try + { + bag.GetStringProperty("key4"); + Assert.Fail("Get String Property must fail for unexisting property"); + } + catch (ArgumentException) + { + } + } + + [Test] + public void TestToDictionary() + { + var properties = bag.ToDictionary(); + Assert.That(CollectionUtil.DictionariesEqual(properties, CreateDictionary()), + "ToDictionary must return the same properties as it was created with" ); + } + + private static PropertyBag CreateBag() + { + var properties = CreateDictionary(); + return new PropertyBag(properties); + } + + private static Dictionary CreateDictionary() + { + var properties = new Dictionary + { + {"key1", "value1"}, + {"key2", null}, + {"key3", (int?) 1}, + {"key5", (long?) 1} + }; + return properties; + } + } +} diff --git a/dotnet/framework/FrameworkTests/ProxyTests.cs b/dotnet/framework/FrameworkTests/ProxyTests.cs new file mode 100644 index 00000000..968986bb --- /dev/null +++ b/dotnet/framework/FrameworkTests/ProxyTests.cs @@ -0,0 +1,96 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using NUnit.Framework; +using System.Reflection; +using Org.IdentityConnectors.Common.Proxy; +using System.Collections.Generic; + +namespace FrameworkTests +{ + public interface MyTestInterface + { + string TestProperty { get; set; } + + String TestTwoArgs(string val1, string val2); + void TestVoid(IList list); + int TestPrimitive(int arg); + DateTime TestStruct(DateTime arg); + } + + [TestFixture] + public class ProxyTests + { + public class MyHandler : InvocationHandler + { + private string _testProperty; + + public Object Invoke(Object proxy, MethodInfo method, Object[] args) + { + if (method.Name.Equals("TestTwoArgs")) + { + return "" + method.Name + " " + args[0] + " " + args[1]; + } + else if (method.Name.Equals("TestVoid")) + { + IList arg = (IList)args[0]; + arg.Add("my void result"); + return null; + } + else if (method.Name.Equals("get_TestProperty")) + { + return _testProperty; + } + else if (method.Name.Equals("set_TestProperty")) + { + _testProperty = (string)args[0]; + return null; + } + else + { + return args[0]; + } + } + } + + [Test] + public void TestProxy() + { + InvocationHandler handler = new MyHandler(); + + MyTestInterface inter = + (MyTestInterface)Proxy.NewProxyInstance(typeof(MyTestInterface), + handler); + Assert.AreEqual("TestTwoArgs foo bar", + inter.TestTwoArgs("foo", "bar")); + IList temp = new List(); + inter.TestVoid(temp); + Assert.AreEqual("my void result", temp[0]); + Assert.AreEqual(10, inter.TestPrimitive(10)); + Assert.AreEqual(1000L, inter.TestStruct(new DateTime(1000)).Ticks); + inter.TestProperty = "my property"; + Assert.AreEqual("my property", inter.TestProperty); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/SafeTypeTest.cs b/dotnet/framework/FrameworkTests/SafeTypeTest.cs new file mode 100644 index 00000000..441b629a --- /dev/null +++ b/dotnet/framework/FrameworkTests/SafeTypeTest.cs @@ -0,0 +1,46 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ + +using NUnit.Framework; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Api.Operations; +namespace FrameworkTests +{ + [TestFixture] + public class SafeTypeTest + { + [Test] + public void TestSafeType() + { + //compile-time type safe + SafeType op = + SafeType.Get(); + Assert.AreEqual(typeof(ScriptOnResourceApiOp), op.RawType); + //runtime type safe. needed for marshalling code, etc + op = + SafeType.ForRawType(typeof(SchemaApiOp)); + Assert.AreEqual(typeof(SchemaApiOp), op.RawType); + } + } +} diff --git a/dotnet/framework/FrameworkTests/ScriptTests.cs b/dotnet/framework/FrameworkTests/ScriptTests.cs new file mode 100644 index 00000000..697ea8fe --- /dev/null +++ b/dotnet/framework/FrameworkTests/ScriptTests.cs @@ -0,0 +1,146 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using System.Reflection; +using Org.IdentityConnectors.Common.Script; +using Org.IdentityConnectors.Framework.Common.Objects; +using NUnit.Framework; + +namespace FrameworkTests +{ + /// + /// Description of ScriptTests. + /// + [TestFixture] + public class ScriptTests + { + [Test] + public void testBooScripting() + { + ScriptExecutorFactory factory = ScriptExecutorFactory.NewInstance("BOO"); + ScriptExecutor exe = factory.NewScriptExecutor(new Assembly[0], "x", false); + IDictionary vals = new Dictionary(); + vals["x"] = 1; + Assert.AreEqual(1, exe.Execute(vals)); + vals["x"] = 2; + Assert.AreEqual(2, exe.Execute(vals)); + } + [Test] + public void testShellScripting() + { + ScriptExecutorFactory factory = ScriptExecutorFactory.NewInstance("Shell"); + ScriptExecutor exe = factory.NewScriptExecutor(new Assembly[0], "echo bob", false); + IDictionary vals = new Dictionary(); + Assert.AreEqual(0, ((IDictionary)exe.Execute(vals))["exitCode"]); + } + [Test] + [ExpectedException(typeof(ArgumentException))] + public void testUnsupported() + { + ScriptExecutorFactory.NewInstance("fadsflkj"); + } + + [Test] + public void testBasic() + { + ScriptBuilder builder = new ScriptBuilder(); + builder.ScriptLanguage = "Groovy"; + builder.ScriptText = "print 'foo'"; + Script s1 = builder.Build(); + Assert.AreEqual("Groovy", s1.ScriptLanguage); + Assert.AreEqual("print 'foo'", s1.ScriptText); + builder = new ScriptBuilder(); + builder.ScriptLanguage = "Groovy"; + builder.ScriptText = "print 'foo'"; + Script s2 = builder.Build(); + Assert.AreEqual(s1, s2); + Assert.AreEqual(s1.GetHashCode(), s2.GetHashCode()); + } + + [Test] + public void testLanguageNotBlank() + { + try + { + ScriptBuilder builder = new ScriptBuilder(); + builder.ScriptText = "print 'foo'"; + builder.Build(); + Assert.Fail(); + } + catch (ArgumentException) + { + // OK. + } + + try + { + ScriptBuilder builder = new ScriptBuilder(); + builder.ScriptText = "print 'foo'"; + builder.ScriptLanguage = ""; + builder.Build(); + Assert.Fail(); + } + catch (ArgumentException) + { + // OK. + } + + try + { + ScriptBuilder builder = new ScriptBuilder(); + builder.ScriptText = "print 'foo'"; + builder.ScriptLanguage = " "; + builder.Build(); + Assert.Fail(); + } + catch (ArgumentException) + { + // OK. + } + } + + [Test] + public void testTextNotNull() + { + ScriptBuilder builder = new ScriptBuilder(); + try + { + builder.ScriptLanguage = "Groovy"; + builder.Build(); + Assert.Fail(); + } + catch (ArgumentNullException) + { + // OK. + } + + // The text can be empty. + builder = new ScriptBuilder(); + builder.ScriptLanguage = "Groovy"; + builder.ScriptText = ""; + builder.Build(); + } + } +} diff --git a/dotnet/framework/FrameworkTests/StringUtilTests.cs b/dotnet/framework/FrameworkTests/StringUtilTests.cs new file mode 100755 index 00000000..94fc6007 --- /dev/null +++ b/dotnet/framework/FrameworkTests/StringUtilTests.cs @@ -0,0 +1,224 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using Org.IdentityConnectors.Common; +using NUnit.Framework; + +namespace FrameworkTests +{ + /// + /// Description of ScriptTests. + /// + [TestFixture] + public class StringUtilTests + { + [Test] + public virtual void TestIndexOfDigit() + { + int test = 0; + const string TEST0 = null; + const string TEST1 = "fsadlkjffj"; + const string TEST2 = "abac2dafj"; + const string TEST3 = "fa323jf4af"; + + test = StringUtil.IndexOfDigit(TEST0); + Assert.AreEqual(test, -1); + test = StringUtil.IndexOfDigit(TEST1); + Assert.AreEqual(test, -1); + test = StringUtil.IndexOfDigit(TEST2); + Assert.AreEqual(test, 4); + test = StringUtil.IndexOfDigit(TEST3); + Assert.AreEqual(test, 2); + } + + [Test] + public virtual void TestIndexOfNonDigit() + { + int test = 0; + const string TEST0 = null; + const string TEST1 = "2131398750976"; + const string TEST2 = "21312a9320484"; + const string TEST3 = "32323aa323435"; + + test = StringUtil.IndexOfNonDigit(TEST0); + Assert.AreEqual(test, -1); + test = StringUtil.IndexOfNonDigit(TEST1); + Assert.AreEqual(test, -1); + test = StringUtil.IndexOfNonDigit(TEST2); + Assert.AreEqual(test, 5); + test = StringUtil.IndexOfNonDigit(TEST3); + Assert.AreEqual(test, 5); + } + + [Test] + public virtual void TestSubDigitString() + { + } + + [Test] + public virtual void TestStripXmlAttribute() + { + string[][] DATA = new string[][] { new string[] { null, null, null }, new string[] { "attr='fads'", "attr", "" }, new string[] { "at1='fasd' at1=''", "at1", "" } }; + string tst = null; + for (int i = 0; i < DATA.Length; i++) + { + tst = StringUtil.StripXmlAttribute(DATA[i][0], DATA[i][1]); + Assert.AreEqual(tst, DATA[i][2]); + } + } + + /// + /// Make sure it removes '\n'. + /// + [Test] + public virtual void TestStripNewlines() + { + string[][] TESTS = new string[][] { new string[] { null, null }, new string[] { "afdslf\n", "afdslf" }, new string[] { "afds\nfadkfj", "afdsfadkfj" }, new string[] { "afds \nfadkfj", "afds fadkfj" }, new string[] { "afds\n fadkfj", "afds fadkfj" } }; + string tmp; + foreach (string[] data in TESTS) + { + tmp = StringUtil.StripNewlines(data[0]); + Assert.AreEqual(tmp, data[1]); + } + } + + [Test] + public virtual void TestStripXmlComments() + { + string[][] DATA = new string[][] { new string[] { null, null }, new string[] { "", "" }, new string[] { "test data", "test data" }, new string[] { "", "test data-->" }, new string[] { "test data ", "test data " }, new string[] { " test data", " test data" }, new string[] { " test data", " test data" } }; + + string tst = null; + for (int i = 0; i < DATA.Length; i++) + { + tst = StringUtil.StripXmlComments(DATA[i][0]); + Assert.AreEqual(tst, DATA[i][1]); + } + } + + /// + /// Tests the methods on the + /// arguments provided. + /// + internal static void ParseLineTest(char textQ, char fieldD, IList values) + { + string csv = CreateCSVLine(textQ, fieldD, values); + IList parsedValues = StringUtil.ParseLine(csv, fieldD, textQ); + Assert.AreEqual(parsedValues, ToStringList(values)); + } + + /// + /// Create a CSV line based on the values provided. + /// + internal static string CreateCSVLine(char textQ, char fieldD, IList values) + { + StringBuilder bld = new StringBuilder(); + bool first = true; + foreach (object o in values) + { + // apply field delimiter.. + if (first) + { + first = false; + } + else + { + bld.Append(fieldD); + } + // if its a string add the text qualifiers.. + // don't bother escape text qualifiers in the string yet.. + if (o is string) + { + bld.Append(textQ); + } + bld.Append(o); + if (o is string) + { + bld.Append(textQ); + } + } + return bld.ToString(); + } + + /// + /// Converts a of objects to a of s. + /// + internal static IList ToStringList(IList list) + { + IList ret = new List(); + foreach (object o in list) + { + ret.Add(o.ToString()); + } + return ret; + } + + internal static IList RandomList(Random r, int size, char[] invalid, char valid) + { + IList ret = new List(); + for (int i = 0; i < size; i++) + { + object add; + if (r.Next(100) % 2 == 0) + { + add = r.Next(); + } + else if (r.Next(100) % 2 == 0) + { + add = r.NextDouble(); + } + else + { + string str = StringUtil.RandomString(r, r.Next(30)); + foreach (char c in invalid) + { + // replace all w/ 'a'.. + str = str.Replace(c, valid); + } + add = str; + } + ret.Add(add); + } + return ret; + } + + [Test] + public virtual void TestRandomString() + { + // just execute it because it doesn't really matter.. + string s = StringUtil.RandomString(); + Assert.IsTrue(s.Length < 257); + } + + [Test] + public virtual void testEndsWith() + { + Assert.IsTrue(StringUtil.EndsWith("afdsf", 'f')); + Assert.IsFalse(StringUtil.EndsWith(null, 'f')); + Assert.IsFalse(StringUtil.EndsWith("fadsfkj", 'f')); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/TestHelperTests.cs b/dotnet/framework/FrameworkTests/TestHelperTests.cs new file mode 100644 index 00000000..4f00a155 --- /dev/null +++ b/dotnet/framework/FrameworkTests/TestHelperTests.cs @@ -0,0 +1,494 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +using System.Collections.Generic; +using NUnit.Framework; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Test.Common; +using System.Text; +using System.Diagnostics; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.TestConnector; + +namespace FrameworkTests +{ + /// + /// Description of TestHelperTests. + /// + [TestFixture] + public class TestHelperTests + { + [Test] + public void TestReadConfiguration() + { + using (var memoryStream = new MemoryStream()) + { + var properties = new Dictionary() + { + {"bob", "bobsValue"}, + {"bob2", "bob2sValue"} + }; + + CreateXmlConfiguration(memoryStream, properties); + + memoryStream.Seek(0, SeekOrigin.Begin); + // load the properties files + var dict = TestHelpers.ReadConfiguration(memoryStream); + foreach (var property in properties) + { + Assert.AreEqual(dict[property.Key], property.Value); + } + } + } + + /// + /// Creates an XML configuration in the specified stream based on the . + /// + /// The output stream. + /// The properties to be stored. + /// The caller is responsible for closing the stream. + private static void CreateXmlConfiguration(Stream stream, IDictionary properties) + { + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CloseOutput = false + }; + using (var writer = XmlWriter.Create(stream, settings)) + { + writer.WriteStartDocument(); + writer.WriteStartElement("config"); + foreach (var property in properties) + { + writer.WriteStartElement("property"); + writer.WriteAttributeString("name", property.Key); + writer.WriteAttributeString("value", property.Value); + writer.WriteEndElement(); + } + writer.WriteEndDocument(); + writer.Flush(); + } + } + + [Test] + public void TestGetProperties() + { + const string testConfigName = "myconfig"; + Type connectorType = typeof(Org.IdentityConnectors.TestConnector.FakeConnector); + + //store environment variables, they must be restored at the end + var oldTestConfig = Environment.GetEnvironmentVariable(TestHelpers.TestConfigEVName); + var oldPrivateConfigRoot = Environment.GetEnvironmentVariable(TestHelpers.PrivateConfigRootEVName); + Environment.SetEnvironmentVariable(TestHelpers.TestConfigEVName, testConfigName); + + var privateConfigRoot = Path.GetTempPath(); + + try + { + //set the TestHelpers.PrivateConfigRootEVName environment variable used by this test + Environment.SetEnvironmentVariable(TestHelpers.PrivateConfigRootEVName, privateConfigRoot); + + //at the end the created dir structure must be deleted, hence we need to store this + privateConfigRoot = Path.Combine(privateConfigRoot, "config"); + + var privateConfigPath = Path.Combine(Path.Combine(privateConfigRoot, connectorType.FullName), + "config-private"); + //create the directory structure for the general and the specific "testConfigName" private config + Directory.CreateDirectory(Path.Combine(privateConfigPath, testConfigName)); + + //create general private config file + using (var configFile = File.Create(Path.Combine(privateConfigPath, "config.xml"))) + { + CreateXmlConfiguration(configFile, new Dictionary() { { "privatekey", "value" } }); + } + + //create specific private config file + using (var configFile = File.Create(Path.Combine(Path.Combine(privateConfigPath, testConfigName), "config.xml"))) + { + CreateXmlConfiguration(configFile, new Dictionary() { { "myconfig.privatekey", "value" } }); + } + + PropertyBag bag1 = TestHelpers.GetProperties(connectorType); + CheckProperties(bag1); + PropertyBag bag2 = TestHelpers.GetProperties(connectorType); + Assert.AreSame(bag1, bag2, "TestHepers must create the same PropertyBag for the same connector"); + } + finally + { + if (oldTestConfig != null) + { + Environment.SetEnvironmentVariable(TestHelpers.TestConfigEVName, oldTestConfig); + } + if (oldPrivateConfigRoot != null) + { + Environment.SetEnvironmentVariable(TestHelpers.PrivateConfigRootEVName, oldPrivateConfigRoot); + } + + try + { + if (Directory.Exists(privateConfigRoot)) + { + Directory.Delete(privateConfigRoot, true); + } + } + catch (Exception ex) + { + //although, something bad happened there is no need to fail the test since the next time it will overwrite + //the temporary files if any exists + Trace.TraceWarning(ex.ToString()); + } + } + } + + private static void CheckProperties(PropertyBag bag) + { + var properties = new Dictionary + { + {"Help", "Me"}, + {"publickey", "value"}, + {"myconfig.publickey", "value"}, + {"privatekey", "value"}, + {"myconfig.privatekey", "value"} + }; + + var bagElements = bag.ToDictionary(); + Assert.That( + CollectionUtil.DictionariesEqual(properties, bagElements) && CollectionUtil.DictionariesEqual(bagElements, properties), + "Expected test properties not equal"); + } + + [Test] + public void TestFillConfiguration() + { + TestConfiguration testConfig = new TestConfiguration(); + // There is no "Foo" property in the config bean. We want to ensure + // that TestHelpers.FillConfiguration() does not fail for unknown properties. + IDictionary configData = new Dictionary(); + configData["Host"] = "example.com"; + configData["Port"] = 1234; + configData["Foo"] = "bar"; + TestHelpers.FillConfiguration(testConfig, configData); + + Assert.AreEqual("example.com", testConfig.Host); + Assert.AreEqual(1234, testConfig.Port); + } + + [Test] + public void TestCreateTestConfiguration() + { + IDictionary expectedData = new Dictionary(); + expectedData["String"] = "retipipiter"; + expectedData["StringArray"] = new [] { "value1", "value2", "value3" }; + expectedData["Long"] = 11L; + expectedData["LongArray"] = new []{12L, 13L}; + expectedData["LongObject"] = 14L; + expectedData["LongObjectArray"] = new long?[]{15, null}; + expectedData["Char"] = 'a'; + expectedData["CharArray"] = new []{'b','c'}; + expectedData["Character"] = 'd'; + expectedData["CharacterArray"] = new char?[]{'e','f'}; + expectedData["Double"] = 0D; + expectedData["DoubleArray"] = new []{0D, 100D}; + expectedData["DoubleObject"] = 0d; + expectedData["DoubleObjectArray"] = new double?[] { 0D, 100D }; + expectedData["Float"] = 0F; + expectedData["FloatArray"] = new[] { 0F, 100F }; + expectedData["FloatObject"] = null; + expectedData["FloatObjectArray"] = new float?[] { 0F, 100F }; + expectedData["Int"] = 0; + expectedData["IntArray"] = new[] { 0, 100 }; + expectedData["Integer"] = 0; + expectedData["IntegerArray"] = new int?[] { 0, 100 }; + expectedData["Boolean"] = true; + expectedData["BooleanArray"] = new[]{true, false}; + expectedData["BooleanObject"] = false; + expectedData["BooleanObjectArray"] = new bool?[] { true, false }; + expectedData["URI"] = new Uri("http://localhost:8080"); expectedData["URIArray"] = ""; + expectedData["URIArray"] = new[] { new Uri("http://localhost:8080"), new Uri("http://localhost:8443") }; + expectedData["File"] = new FileName("c:\\Users\\Admin"); + expectedData["FileArray"] = new[] {new FileName("c:\\Users\\Admin\\Documents"), new FileName("c:\\Users\\Admin\\Settings")}; + var array = new GuardedByteArray(); + Encoding.UTF8.GetBytes("array").ToList().ForEach(array.AppendByte); + expectedData["GuardedByteArray"] = array; + + array = new GuardedByteArray(); + Encoding.UTF8.GetBytes("item1").ToList().ForEach(array.AppendByte); + var array2 = new GuardedByteArray(); + Encoding.UTF8.GetBytes("item2").ToList().ForEach(array2.AppendByte); + expectedData["GuardedByteArrayArray"] = new []{array, array2}; + + var secret = new GuardedString(); + "secret".ToCharArray().ToList().ForEach(secret.AppendChar); + expectedData["GuardedString"] = secret; + + secret = new GuardedString(); + "secret1".ToCharArray().ToList().ForEach(secret.AppendChar); + var secret2 = new GuardedString(); + "secret2".ToCharArray().ToList().ForEach(secret2.AppendChar); + + expectedData["GuardedStringArray"] = new[]{secret, secret2}; + expectedData["Script"] = new ScriptBuilder { ScriptLanguage = "PowerShell", ScriptText = "echo 'Hello OpenICF Developer'" }.Build(); + expectedData["ScriptArray"] = new[]{new ScriptBuilder { ScriptLanguage = "Groovy", ScriptText = "println 'Hello'" }.Build(),new ScriptBuilder { ScriptLanguage = "Groovy", ScriptText = "println 'OpenICF Developer'" }.Build()}; + + Environment.SetEnvironmentVariable(TestHelpers.TestConfigEVName, "converter"); + + FieldInfo info = typeof (TestHelpers).GetField("_propertyBags", BindingFlags.NonPublic | BindingFlags.Static); + (info.GetValue(null) as Dictionary).Clear(); + + PropertyBag propertyBag = + TestHelpers.GetProperties(typeof(Org.IdentityConnectors.TestConnector.FakeConnector)); + (info.GetValue(null) as Dictionary).Clear(); + + APIConfiguration testable = TestHelpers.CreateTestConfiguration(SafeType.Get(), propertyBag, null); + + foreach (KeyValuePair entry in expectedData) + { + Assert.AreEqual(entry.Value, testable.ConfigurationProperties.GetProperty(entry.Key).Value, "Configuration property: " + entry.Key + " has different value"); + } + } + + } +} + +namespace Org.IdentityConnectors.TestConnector +{ + + + public class TestConfiguration : Configuration + { + + public ConnectorMessages ConnectorMessages + { + get + { + return null; + } + set + { + Assert.Fail("Should not set ConnectorMessages"); + } + } + + public void Validate() + { + Assert.Fail("Should not call Validate()"); + } + + public String Host { get; set; } + + public int Port { get; set; } + + public String Unused { get; set; } + + + //string + + [ConfigurationProperty(Order = 1)] + public string String { get; set; } + + [ConfigurationProperty(Order = 2)] + public string[] StringArray { get; set; } + + + // long + + [ConfigurationProperty(Order = 3)] + public long Long { get; set; } + + [ConfigurationProperty(Order = 4)] + public virtual long[] LongArray { get; set; } + + + // Long + + [ConfigurationProperty(Order = 5)] + public long? LongObject { get; set; } + + [ConfigurationProperty(Order = 6)] + public long?[] LongObjectArray { get; set; } + + + // char + + [ConfigurationProperty(Order = 7)] + public char Char { get; set; } + + [ConfigurationProperty(Order = 8)] + public char[] CharArray { get; set; } + + + // Character + + [ConfigurationProperty(Order = 9)] + public char? Character { get; set; } + + [ConfigurationProperty(Order = 10)] + public char?[] CharacterArray { get; set; } + + + // double + + [ConfigurationProperty(Order = 11)] + public double Double { get; set; } + + [ConfigurationProperty(Order = 12)] + public double[] DoubleArray { get; set; } + + + // Double + + [ConfigurationProperty(Order = 13)] + public double? DoubleObject { get; set; } + + [ConfigurationProperty(Order = 14)] + public double?[] DoubleObjectArray { get; set; } + + + // float + + [ConfigurationProperty(Order = 15)] + public float Float { get; set; } + + [ConfigurationProperty(Order = 16)] + public float[] FloatArray { get; set; } + + + // Float + + [ConfigurationProperty(Order = 17)] + public float? FloatObject { get; set; } + + [ConfigurationProperty(Order = 18)] + public float?[] FloatObjectArray { get; set; } + + + // int + + [ConfigurationProperty(Order = 19)] + public int Int { get; set; } + + [ConfigurationProperty(Order = 20)] + public int[] IntArray { get; set; } + + + // Integer + + [ConfigurationProperty(Order = 21)] + public int? Integer { get; set; } + + [ConfigurationProperty(Order = 22)] + public int?[] IntegerArray { get; set; } + + + // boolean + + [ConfigurationProperty(Order = 23)] + public bool Boolean { get; set; } + + [ConfigurationProperty(Order = 24)] + public bool[] BooleanArray { get; set; } + + + // Boolean + + [ConfigurationProperty(Order = 25)] + public bool? BooleanObject { get; set; } + + [ConfigurationProperty(Order = 26)] + public bool?[] BooleanObjectArray { get; set; } + + + // URI + + [ConfigurationProperty(Order = 27)] + public Uri URI { get; set; } + + [ConfigurationProperty(Order = 28)] + public Uri[] URIArray { get; set; } + + + // File + + [ConfigurationProperty(Order = 29)] + public FileName File { get; set; } + + [ConfigurationProperty(Order = 30)] + public virtual FileName[] FileArray { get; set; } + + + // GuardedByteArray + + [ConfigurationProperty(Order = 31)] + public GuardedByteArray GuardedByteArray { get; set; } + + [ConfigurationProperty(Order = 32)] + public GuardedByteArray[] GuardedByteArrayArray { get; set; } + + + // GuardedString + + [ConfigurationProperty(Order = 33)] + public GuardedString GuardedString { get; set; } + + [ConfigurationProperty(Order = 34)] + public GuardedString[] GuardedStringArray { get; set; } + + + // Script + + [ConfigurationProperty(Order = 35)] + public Script Script { get; set; } + + [ConfigurationProperty(Order = 36)] + public Script[] ScriptArray { get; set; } + } + + [ConnectorClass("FakeConnector", + "FakeConnector.category", + typeof(TestConfiguration) + )] + public class FakeConnector : Connector + { + #region Connector Members + public void Init(Configuration configuration) + { + throw new NotImplementedException(); + } + #endregion + + #region IDisposable Members + public void Dispose() + { + throw new NotImplementedException(); + } + #endregion + } +} diff --git a/dotnet/framework/FrameworkTests/TestUtil.cs b/dotnet/framework/FrameworkTests/TestUtil.cs new file mode 100644 index 00000000..8b1fc9c4 --- /dev/null +++ b/dotnet/framework/FrameworkTests/TestUtil.cs @@ -0,0 +1,53 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +using System; +using System.Diagnostics; + +namespace FrameworkTests +{ + /// + /// Description of TestUtil. + /// + public static class TestUtil + { + private static readonly object LOCK = new Object(); + private static bool _initialized; + + + public static void InitializeLogging() + { + lock (LOCK) + { + if (!_initialized) + { + ConsoleTraceListener listener = + new ConsoleTraceListener(); + listener.TraceOutputOptions = + TraceOptions.ThreadId | TraceOptions.Timestamp; + Trace.Listeners.Add(listener); + _initialized = true; + } + } + } + } +} diff --git a/dotnet/framework/FrameworkTests/UpdateImplTests.cs b/dotnet/framework/FrameworkTests/UpdateImplTests.cs new file mode 100644 index 00000000..013b7ece --- /dev/null +++ b/dotnet/framework/FrameworkTests/UpdateImplTests.cs @@ -0,0 +1,226 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Security; +using NUnit.Framework; +using System.Collections.Generic; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Impl.Api.Local.Operations; +using Org.IdentityConnectors.Framework.Common.Objects; +namespace FrameworkTests +{ + /// + /// Description of UpdateImplTests. + /// + [TestFixture] + public class UpdateImplTests + { + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ValidateUidArg() + { + UpdateImpl.ValidateInput(ObjectClass.ACCOUNT, null, new HashSet(), true); + } + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ValidateObjectClassArg() + { + UpdateImpl.ValidateInput(null, new Uid("foo"), new HashSet(), true); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ValidateAttrsArg() + { + UpdateImpl.ValidateInput(ObjectClass.ACCOUNT, new Uid("foo"), null, true); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void ValidateUidAttribute() + { + HashSet attrs = new HashSet(); + attrs.Add(new Uid("foo")); + UpdateImpl.ValidateInput(ObjectClass.ACCOUNT, new Uid("foo"), attrs, true); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void ValidateAddWithNullAttribute() + { + ICollection attrs = new HashSet(); + attrs.Add(ConnectorAttributeBuilder.Build("something")); + UpdateImpl.ValidateInput(ObjectClass.ACCOUNT, new Uid("foo"), attrs, true); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void ValidateAttemptToAddName() + { + ICollection attrs = new HashSet(); + attrs.Add(new Name("fadf")); + UpdateImpl.ValidateInput(ObjectClass.ACCOUNT, new Uid("foo"), attrs, true); + } + + [Test] + public void ValidateAttemptToAddDeleteOperationalAttribute() + { + // list of all the operational attributes.. + ICollection list = new List(); + list.Add(ConnectorAttributeBuilder.BuildEnabled(false)); + list.Add(ConnectorAttributeBuilder.BuildLockOut(true)); + list.Add(ConnectorAttributeBuilder.BuildCurrentPassword(newSecureString("fadsf"))); + list.Add(ConnectorAttributeBuilder.BuildPasswordExpirationDate(DateTime.Now)); + list.Add(ConnectorAttributeBuilder.BuildPassword(newSecureString("fadsf"))); + foreach (ConnectorAttribute attr in list) + { + ICollection attrs = new HashSet(); + attrs.Add(attr); + try + { + UpdateImpl.ValidateInput(ObjectClass.ACCOUNT, new Uid("1"), attrs, true); + Assert.Fail("Failed: " + attr.Name); + } + catch (ArgumentException) + { + // this is a good thing.. + } + } + } + + private static SecureString newSecureString(string password) + { + SecureString rv = new SecureString(); + foreach (char c in password.ToCharArray()) + { + rv.AppendChar(c); + } + return rv; + } + + /// + /// Validate two collections are equal. (Not fast but effective) + /// + public static bool AreEqual(ICollection arg1, + ICollection arg2) + { + if (arg1.Count != arg2.Count) + { + return false; + } + foreach (ConnectorAttribute attr in arg1) + { + if (!arg2.Contains(attr)) + { + return false; + } + } + return true; + } + [Test] + public void MergeAddAttribute() + { + UpdateImpl up = new UpdateImpl(null, null); + ICollection actual; + ICollection baseAttrs = CollectionUtil.NewSet(); + ICollection expected = CollectionUtil.NewSet(); + ICollection changeset = CollectionUtil.NewSet(); + // attempt to add a value to an attribute.. + ConnectorAttribute cattr = ConnectorAttributeBuilder.Build("abc", 2); + changeset.Add(cattr); + expected.Add(ConnectorAttributeBuilder.Build("abc", 2)); + actual = up.Merge(changeset, baseAttrs, true); + Assert.IsTrue(AreEqual(expected, actual)); + } + + [Test] + public void MergeAddToExistingAttribute() + { + UpdateImpl up = new UpdateImpl(null, null); + ICollection actual; + ICollection baseAttrs = CollectionUtil.NewSet(); + ICollection expected = CollectionUtil.NewSet(); + ICollection changeset = CollectionUtil.NewSet(); + // attempt to add a value to an attribute.. + ConnectorAttribute battr = ConnectorAttributeBuilder.Build("abc", 1); + ConnectorAttribute cattr = ConnectorAttributeBuilder.Build("abc", 2); + baseAttrs.Add(battr); + changeset.Add(cattr); + expected.Add(ConnectorAttributeBuilder.Build("abc", 1, 2)); + actual = up.Merge(changeset, baseAttrs, true); + Assert.IsTrue(AreEqual(expected, actual)); + } + + [Test] + public void MergeDeleteNonExistentAttribute() + { + UpdateImpl up = new UpdateImpl(null, null); + ICollection actual; + ICollection baseAttrs = CollectionUtil.NewSet(); + ICollection expected = CollectionUtil.NewSet(); + ICollection changeset = CollectionUtil.NewSet(); + // attempt to add a value to an attribute.. + ConnectorAttribute cattr = ConnectorAttributeBuilder.Build("abc", 2); + changeset.Add(cattr); + actual = up.Merge(changeset, baseAttrs, false); + Assert.IsTrue(AreEqual(expected, actual)); + } + + [Test] + public void MergeDeleteToExistingAttribute() + { + UpdateImpl up = new UpdateImpl(null, null); + ICollection actual; + ICollection baseAttrs = CollectionUtil.NewSet(); + ICollection expected = CollectionUtil.NewSet(); + ICollection changeset = CollectionUtil.NewSet(); + // attempt to add a value to an attribute.. + ConnectorAttribute battr = ConnectorAttributeBuilder.Build("abc", 1, 2); + ConnectorAttribute cattr = ConnectorAttributeBuilder.Build("abc", 2); + baseAttrs.Add(battr); + changeset.Add(cattr); + expected.Add(ConnectorAttributeBuilder.Build("abc", 1)); + actual = up.Merge(changeset, baseAttrs, false); + Assert.IsTrue(AreEqual(expected, actual)); + } + + [Test] + public void MergeDeleteToExistingAttributeCompletely() + { + UpdateImpl up = new UpdateImpl(null, null); + ICollection actual; + ICollection baseAttrs = CollectionUtil.NewSet(); + ICollection expected = CollectionUtil.NewSet(); + ICollection changeset = CollectionUtil.NewSet(); + // attempt to add a value to an attribute.. + ConnectorAttribute battr = ConnectorAttributeBuilder.Build("abc", 1, 2); + ConnectorAttribute cattr = ConnectorAttributeBuilder.Build("abc", 1, 2); + baseAttrs.Add(battr); + changeset.Add(cattr); + expected.Add(ConnectorAttributeBuilder.Build("abc")); + actual = up.Merge(changeset, baseAttrs, false); + Assert.IsTrue(AreEqual(expected, actual)); + } + } +} diff --git a/dotnet/framework/FrameworkTests/VersionRangeTests.cs b/dotnet/framework/FrameworkTests/VersionRangeTests.cs new file mode 100755 index 00000000..96a032ba --- /dev/null +++ b/dotnet/framework/FrameworkTests/VersionRangeTests.cs @@ -0,0 +1,166 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2013-2015 ForgeRock AS. All Rights Reserved + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using NUnit.Framework; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Common; + +namespace FrameworkTests +{ + [TestFixture] + public class VersionRangeTests + { + [Test] + public virtual void TestIsInRange() + { + Version reference0 = new Version(1, 1, 0, 0); + Version reference1 = new Version(1, 1, 0, 1); + Version reference2 = new Version(1, 1, 0, 2); + Version reference3 = new Version(1, 1, 0, 3); + Version reference4 = new Version(1, 1, 0, 4); + VersionRange range = VersionRange.Parse("[1.1.0.1,1.1.0.3)"); + + Assert.IsFalse(range.IsInRange(reference0)); + Assert.IsTrue(range.IsInRange(reference1)); + Assert.IsTrue(range.IsInRange(reference2)); + Assert.IsFalse(range.IsInRange(reference3)); + Assert.IsFalse(range.IsInRange(reference4)); + } + + [Test] + public virtual void TestIsExact() + { + Assert.IsTrue(VersionRange.Parse("1.1.0.0").Exact); + //Version string portion was too short or too long (major.minor[.build[.revision]]). + //Assert.IsTrue(VersionRange.Parse(" [ 1 , 1 ] ").Exact); + Assert.IsTrue(VersionRange.Parse("[ 1.1 , 1.1 ]").Exact); + Assert.IsTrue(VersionRange.Parse(" [1.1.1 , 1.1.1] ").Exact); + Assert.IsTrue(VersionRange.Parse("[1.1.0.0,1.1.0.0]").Exact); + Assert.IsTrue(VersionRange.Parse("(1.1.0.0,1.1.0.2)").Exact); + } + + [Test] + public virtual void TestIsEmpty() + { + Assert.IsTrue(VersionRange.Parse("(1.1.0.0,1.1.0.0)").Empty); + Assert.IsTrue(VersionRange.Parse("(1.2.0.0,1.1.0.0]").Empty); + } + + [Test] + public virtual void TestValidSyntax() + { + try + { + VersionRange.Parse("(1.1.0.0)"); + Assert.Fail("Invalid syntax not failed"); + } + catch (System.FormatException) + { + // ok + } + try + { + VersionRange.Parse("1.1.0.0,1.1)]"); + Assert.Fail("Invalid syntax not failed"); + } + catch (System.ArgumentException) + { + // ok + } + try + { + VersionRange.Parse("(1.1.0.0-1.1)"); + Assert.Fail("Invalid syntax not failed"); + } + catch (System.ArgumentException) + { + // ok + } + try + { + VersionRange.Parse("1.1.0.0,1.1"); + Assert.Fail("Invalid syntax not failed"); + } + catch (System.ArgumentException) + { + // ok + } + try + { + VersionRange.Parse("( , 1.1)"); + Assert.Fail("Invalid syntax not failed"); + } + catch (System.ArgumentException) + { + // ok + } + } + + [Test] + public virtual void TestIsEqual() + { + VersionRange range1 = VersionRange.Parse("[1.1.0.1,1.1.0.3)"); + VersionRange range2 = VersionRange.Parse(range1.ToString()); + Assert.IsTrue(range1.Equals(range2)); + } + + [Test] + public virtual void TestConnectorKeysInRange() + { + ConnectorKeyRange r1 = + ConnectorKeyRange.NewBuilder() + .SetBundleName("B") + .SetConnectorName("C") + .SetBundleVersion("1.1.0.0") + .Build(); + + ConnectorKeyRange r2 = + ConnectorKeyRange.NewBuilder() + .SetBundleName("B") + .SetConnectorName("C") + .SetBundleVersion("[1.1.0.0,1.2.0.0]") + .Build(); + + ConnectorKey k1 = new ConnectorKey("B", "1.1.0.0", "C"); + ConnectorKey k2 = new ConnectorKey("B", "1.2.0.0", "C"); + + Assert.IsTrue(r1.BundleVersionRange.Exact); + Assert.IsFalse(r2.BundleVersionRange.Exact); + + Assert.IsTrue(r1.IsInRange(k1)); + Assert.IsFalse(r1.IsInRange(k2)); + + Assert.IsTrue(r2.IsInRange(k1)); + Assert.IsTrue(r2.IsInRange(k2)); + + ConnectorKeyRange r45 = + ConnectorKeyRange.NewBuilder().SetBundleName("B").SetConnectorName("C") + .SetBundleVersion("[1.4.0.0,1.5.0.0)").Build(); + Assert.IsTrue(r45.IsInRange(new ConnectorKey("B", "1.4.0.0", "C"))); + + Assert.IsFalse(r45.IsInRange(new ConnectorKey("B", "1.5.0.0", "C"))); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/app.config b/dotnet/framework/FrameworkTests/app.config new file mode 100644 index 00000000..8a99629e --- /dev/null +++ b/dotnet/framework/FrameworkTests/app.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/framework/FrameworkTests/packages.config b/dotnet/framework/FrameworkTests/packages.config new file mode 100755 index 00000000..4b8fa985 --- /dev/null +++ b/dotnet/framework/FrameworkTests/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/FrameworkTests/version.template b/dotnet/framework/FrameworkTests/version.template new file mode 100644 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/FrameworkTests/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/PowerShellScriptExecutorFactory/PowerShellScriptExecutorFactory.cs b/dotnet/framework/PowerShellScriptExecutorFactory/PowerShellScriptExecutorFactory.cs new file mode 100644 index 00000000..c4810972 --- /dev/null +++ b/dotnet/framework/PowerShellScriptExecutorFactory/PowerShellScriptExecutorFactory.cs @@ -0,0 +1,127 @@ +/** +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +* +* Copyright (c) 2012-2014 ForgeRock AS. All Rights Reserved +* +* The contents of this file are subject to the terms +* of the Common Development and Distribution License +* (the License). You may not use this file except in +* compliance with the License. +* +* You can obtain a copy of the License at +* http://forgerock.org/license/CDDLv1.0.html +* See the License for the specific language governing +* permission and limitations under the License. +* +* When distributing Covered Code, include this CDDL +* Header Notice in each file and include the License file +* at http://forgerock.org/license/CDDLv1.0.html +* If applicable, add the following below the CDDL Header, +* with the fields enclosed by brackets [] replaced by +* your own identifying information: +* "Portions Copyrighted [year] [name of copyright owner]" +* +* +* Author: Gael Allioux +*/ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Reflection; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Script; + + +// namespace Org.IdentityConnectors.Common.Script.PowerShell +namespace Org.ForgeRock.OpenICF.Framework.Common.Script.PowerShell +{ + [ScriptExecutorFactoryClass("PowerShell")] + public class PowerShellScriptExecutorFactory : ScriptExecutorFactory + { + + /// + /// Creates a script executor given the PowerShell script. + /// + override + public ScriptExecutor NewScriptExecutor(Assembly[] referencedAssemblies, string script, bool compile) + { + return new PowerShellScriptExecutor(script); + } + + /// + /// Processes the script. + /// + class PowerShellScriptExecutor : ScriptExecutor + { + private readonly string _script; + + public PowerShellScriptExecutor() + { + } + + public PowerShellScriptExecutor(string script) + { + _script = script; + } + + public PowerShellScriptExecutor(Assembly[] referencedAssemblies, string script) + { + _script = script; + } + + public object Execute(IDictionary arguments) + { + Command myCommand = new Command(_script,true); + Runspace runspace = RunspaceFactory.CreateRunspace(); + Pipeline pipeline = null; + Collection results = null; + + //foreach (String argumentName in arguments.Keys) + //{ + // CommandParameter param = new CommandParameter(argumentName, arguments[argumentName]); + // myCommand.Parameters.Add(param); + //} + + try + { + runspace.Open(); + // create a pipeline and give it the command + pipeline = runspace.CreatePipeline(); + pipeline.Commands.Add(myCommand); + //pipeline.Commands.Add("Out-String"); + foreach (String argumentName in arguments.Keys) + { + runspace.SessionStateProxy.SetVariable(argumentName, arguments[argumentName]); + } + // execute the script + results = pipeline.Invoke(); + } + catch (Exception e) + { + TraceUtil.TraceException("Unable to run PowerShell script on resource.", e); + throw; + } + finally + { + pipeline.Dispose(); + // close & dispose the runspace + runspace.Close(); + runspace.Dispose(); + } + + // return the script result as a single string + IDictionary result = new Dictionary(); + int index = 0; + foreach (PSObject obj in results) + { + result.Add(index.ToString(),obj.ToString()); + index++; + } + return result; + } + } + } +} diff --git a/dotnet/framework/PowerShellScriptExecutorFactory/PowerShellScriptExecutorFactory.csproj b/dotnet/framework/PowerShellScriptExecutorFactory/PowerShellScriptExecutorFactory.csproj new file mode 100644 index 00000000..d3d30e0c --- /dev/null +++ b/dotnet/framework/PowerShellScriptExecutorFactory/PowerShellScriptExecutorFactory.csproj @@ -0,0 +1,96 @@ + + + + + {57754FFA-BB1F-4722-A2FA-70C4F27C6784} + Debug + AnyCPU + Library + Properties + Org.ForgeRock.OpenICF.Framework.Common.Script.PowerShell + PowerShell.ScriptExecutorFactory + v4.5.2 + true + + + + prompt + 4 + AnyCPU + bin\Debug\ + True + Full + False + True + DEBUG;TRACE + false + + + pdbonly + bin\Release\ + prompt + true + 4 + AnyCPU + True + False + TRACE + false + + + + + + 4.0 + + + False + + + 4.0 + + + + + + + + + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/PowerShellScriptExecutorFactory/version.template b/dotnet/framework/PowerShellScriptExecutorFactory/version.template new file mode 100644 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/PowerShellScriptExecutorFactory/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/Service/FR_ICF_sq_med.ico b/dotnet/framework/Service/FR_ICF_sq_med.ico new file mode 100755 index 00000000..10240f47 Binary files /dev/null and b/dotnet/framework/Service/FR_ICF_sq_med.ico differ diff --git a/dotnet/framework/Service/Program.cs b/dotnet/framework/Service/Program.cs new file mode 100644 index 00000000..3f8189ef --- /dev/null +++ b/dotnet/framework/Service/Program.cs @@ -0,0 +1,332 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Configuration; +using System.Configuration.Install; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Security.Cryptography; +using System.ServiceProcess; +using Org.IdentityConnectors.Common.Security; +using System.Security.Cryptography.X509Certificates; +using Org.IdentityConnectors.Framework.Service.Properties; + +namespace Org.IdentityConnectors.Framework.Service +{ + + static class Program + { + private const string OPT_SERVICE_NAME = "/serviceName"; + private const string OPT_CERTSTOR_NAME = "/storeName"; + private const string OPT_CERTFILE_NAME = "/certificateFile"; + + private static void Usage() + { + Console.WriteLine("Usage: ConnectorServer.exe [option], where command is one of the following: "); + Console.WriteLine(" /install [/serviceName ] - Installs the service."); + Console.WriteLine(" /uninstall [/serviceName ] - Uninstalls the service."); + Console.WriteLine(" /run - Runs the service from the console."); + Console.WriteLine(" /setKey [] - Sets the connector server key."); + Console.WriteLine(" /setDefaults - Sets default app.config"); + Console.WriteLine(" /storeCertificate [/storeName ] [/certificateFile ]- Stores the Certificate in the storage."); + } + + private static IDictionary ParseOptions(string[] args) + { + IDictionary rv = new Dictionary(); + + for (int i = 1; i < args.Length; i++) + { + String optionName = null; + String opt = args[i].ToLower(); + + if (OPT_SERVICE_NAME.Equals(opt, StringComparison.InvariantCultureIgnoreCase)) + { + optionName = OPT_SERVICE_NAME; + } + else if (OPT_CERTFILE_NAME.Equals(opt, StringComparison.InvariantCultureIgnoreCase)) + { + optionName = OPT_CERTFILE_NAME; + } + else if (OPT_CERTSTOR_NAME.Equals(opt, StringComparison.InvariantCultureIgnoreCase)) + { + optionName = OPT_CERTSTOR_NAME; + } + if (optionName != null) + { + i++; + if (i < args.Length) + { + rv[optionName] = args[i]; + } + else + { + Usage(); + return null; + } + } + else + { + Usage(); + return null; + } + } + return rv; + } + + /// + /// This method starts the service. + /// + static void Main(string[] args) + { + if (args.Length == 0) + { + Usage(); + } + else + { + String cmd = args[0].ToLower(); + if (cmd.Equals("/setkey", StringComparison.InvariantCultureIgnoreCase)) + { + if (args.Length > 2) + { + Usage(); + return; + } + DoSetKey(args.Length > 1 ? args[1] : null); + return; + } + if (cmd.Equals("/setDefaults", StringComparison.InvariantCultureIgnoreCase)) + { + if (args.Length > 1) + { + Usage(); + return; + } + using (var file = new StreamWriter(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile, false)) + { + file.WriteLine(Resources.ResourceManager.GetString("DefaultConfig")); + Console.WriteLine("Default configuration successfully restored."); + } + return; + } + IDictionary options = + ParseOptions(args); + if (options == null) + { + //there's a parse error in the options, return + return; + } + if ("/install".Equals(cmd, StringComparison.InvariantCultureIgnoreCase)) + { + DoInstall(options); + } + else if ("/uninstall".Equals(cmd, StringComparison.InvariantCultureIgnoreCase)) + { + DoUninstall(options); + } + else if ("/run".Equals(cmd, StringComparison.InvariantCultureIgnoreCase)) + { + DoRun(options); + } + else if ("/service".Equals(cmd, StringComparison.InvariantCultureIgnoreCase)) + { + ServiceBase.Run(new ServiceBase[] { new Service() }); + } + else if ("/storecertificate".Equals(cmd, StringComparison.InvariantCultureIgnoreCase)) + { + DoStoreCertificate(options); + } + else + { + Usage(); + return; + } + } + } + + private static void DoInstall(IDictionary options) + { + if (options.ContainsKey(OPT_SERVICE_NAME)) + { + ProjectInstaller.ServiceName = options[OPT_SERVICE_NAME]; + } + TransactedInstaller ti = new TransactedInstaller(); + string[] cmdline = + { + Assembly.GetExecutingAssembly ().Location + }; + AssemblyInstaller ai = new AssemblyInstaller( + cmdline[0], + new string[0]); + ti.Installers.Add(ai); + InstallContext ctx = new InstallContext("install.log", + cmdline); + ti.Context = ctx; + ti.Install(new System.Collections.Hashtable()); + } + + private static void DoUninstall(IDictionary options) + { + if (options.ContainsKey(OPT_SERVICE_NAME)) + { + ProjectInstaller.ServiceName = options[OPT_SERVICE_NAME]; + } + TransactedInstaller ti = new TransactedInstaller(); + string[] cmdline = + { + Assembly.GetExecutingAssembly ().Location + }; + AssemblyInstaller ai = new AssemblyInstaller( + cmdline[0], + new string[0]); + ti.Installers.Add(ai); + InstallContext ctx = new InstallContext("uninstall.log", + cmdline); + ti.Context = ctx; + ti.Uninstall(null); + } + + private static void DoRun(IDictionary options) + { + Service svc = new Service(); + + svc.StartService(new String[0]); + + Console.WriteLine("Press q to shutdown."); + Console.WriteLine("Press t for a thread dump."); + + while (true) + { + ConsoleKeyInfo info = Console.ReadKey(); + if (info.KeyChar == 'q') + { + break; + } + else if (info.KeyChar == 't') + { + svc.DumpRequests(); + } + } + + svc.StopService(); + } + + private static GuardedString ReadPassword() + { + GuardedString rv = new GuardedString(); + while (true) + { + ConsoleKeyInfo info = Console.ReadKey(true); + if (info.Key == ConsoleKey.Enter) + { + Console.WriteLine(); + rv.MakeReadOnly(); + return rv; + } + else + { + Console.Write("*"); + rv.AppendChar(info.KeyChar); + } + } + } + + private static void DoSetKey(string key) + { + GuardedString str; + if (key == null) + { + Console.Write("Please enter the new key: "); + GuardedString v1 = ReadPassword(); + Console.Write("Please confirm the new key: "); + GuardedString v2 = ReadPassword(); + if (!v1.Equals(v2)) + { + Console.WriteLine("Error: Key mismatch."); + return; + } + str = v2; + } + else + { + str = new GuardedString(); + foreach (char c in key) + { + str.AppendChar(c); + } + } + Configuration config = + ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + config.AppSettings.Settings.Remove(Service.PROP_KEY); + config.AppSettings.Settings.Add(Service.PROP_KEY, str.GetBase64SHA1Hash()); + config.Save(ConfigurationSaveMode.Modified); + Console.WriteLine("Key has been successfully updated."); + } + + private static void DoStoreCertificate(IDictionary options) + { + string storeName = options.ContainsKey(OPT_CERTSTOR_NAME) ? options[OPT_CERTSTOR_NAME] : "ConnectorServerSSLCertificate"; + + + if (!options.ContainsKey(OPT_CERTFILE_NAME) || String.IsNullOrEmpty(options[OPT_CERTFILE_NAME])) + { + Usage(); + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException("Missing required argument: " + OPT_CERTFILE_NAME); + } + X509Certificate2 certificate = null; + try + { + certificate = new X509Certificate2(options[OPT_CERTFILE_NAME]); + } + catch (CryptographicException) + { + Console.Write("Please enter the keystore password: "); + GuardedString v1 = ReadPassword(); + certificate = new X509Certificate2(options[OPT_CERTFILE_NAME], SecurityUtil.Decrypt(v1), X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + } + X509Store store = new X509Store(storeName, StoreLocation.LocalMachine); + + store.Open(OpenFlags.ReadWrite); + X509CertificateCollection certificates = store.Certificates; + if (certificates.Count != 0) + { + if (certificates.Count == 1) + { + store.Remove(store.Certificates[0]); + Console.WriteLine("Previous certificate has been removed."); + } + else + { + Console.WriteLine("There are multiple certificates were found. You may point to the wrong store."); + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException("There is supported to be exactly one certificate in the store: " + storeName); + } + } + store.Add(certificate); + store.Close(); + Console.WriteLine("Certificate is stored in " + storeName); + } + } +} \ No newline at end of file diff --git a/dotnet/framework/Service/ProjectInstaller.cs b/dotnet/framework/Service/ProjectInstaller.cs new file mode 100644 index 00000000..ea050069 --- /dev/null +++ b/dotnet/framework/Service/ProjectInstaller.cs @@ -0,0 +1,86 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ + +using System.ComponentModel; +using System.Configuration.Install; +using System.ServiceProcess; + +namespace Org.IdentityConnectors.Framework.Service +{ + [RunInstaller(true)] + public class ProjectInstaller : Installer + { + public static string ServiceName { get; set; } + public static string DisplayName { get; set; } + public static string Description { get; set; } + + static ProjectInstaller() + { + ServiceName = "LegacyConnectorServerService"; + DisplayName = "OpenICF Legacy Connector Server"; + Description = "OpenICF Legacy Connector Server"; + } + + private ServiceProcessInstaller serviceProcessInstaller; + private ServiceInstaller serviceInstaller; + + public ProjectInstaller() + { + serviceProcessInstaller = new ServiceProcessInstaller(); + serviceInstaller = new ServiceInstaller(); + + // Here you can set properties on serviceProcessInstaller or register event handlers + serviceProcessInstaller.Account = ServiceAccount.LocalSystem; + + serviceInstaller.ServiceName = ServiceName; + serviceInstaller.Description = Description; + serviceInstaller.DisplayName = DisplayName; + serviceInstaller.StartType = ServiceStartMode.Automatic; + + this.Installers.AddRange(new Installer[] { serviceProcessInstaller, serviceInstaller }); + } + + protected virtual string AppendPathParameter(string path, string parameter) + { + if (path.Length > 0 && path[0] != '"') + { + path = "\"" + path + "\""; + } + path += " " + parameter; + return path; + } + + protected override void OnBeforeInstall(System.Collections.IDictionary savedState) + { + Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service"); + base.OnBeforeInstall(savedState); + } + + protected override void OnBeforeUninstall(System.Collections.IDictionary savedState) + { + Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service"); + base.OnBeforeUninstall(savedState); + } + } +} diff --git a/dotnet/framework/Service/Properties/Resources.Designer.cs b/dotnet/framework/Service/Properties/Resources.Designer.cs new file mode 100755 index 00000000..4bc9e4f4 --- /dev/null +++ b/dotnet/framework/Service/Properties/Resources.Designer.cs @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34209 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Org.IdentityConnectors.Framework.Service.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Org.IdentityConnectors.Framework.Service.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?> + ///<configuration> + /// <runtime> + /// <loadFromRemoteSources enabled="true"/> + /// </runtime> + /// <connectionStrings> + /// <!-- Example connection to a SQL Server Database on localhost. --> + /// <!-- <add name="ExampleConnectionString" + /// connectionString="Data Source=.;Initial Catalog=DBName;Integrated Security=True" + /// providerName="System.Data.SqlClient" /> --> + /// </connectionStrings> + /// <appSettings> + /// <!-- access these values via the property: + /// [rest of string was truncated]";. + /// + internal static string DefaultConfig { + get { + return ResourceManager.GetString("DefaultConfig", resourceCulture); + } + } + } +} diff --git a/dotnet/framework/Service/Properties/Resources.resx b/dotnet/framework/Service/Properties/Resources.resx new file mode 100755 index 00000000..6d0123b9 --- /dev/null +++ b/dotnet/framework/Service/Properties/Resources.resx @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <?xml version="1.0" encoding="utf-8"?> +<configuration> + <runtime> + <loadFromRemoteSources enabled="true"/> + </runtime> + <connectionStrings> + <!-- Example connection to a SQL Server Database on localhost. --> + <!-- <add name="ExampleConnectionString" + connectionString="Data Source=.;Initial Catalog=DBName;Integrated Security=True" + providerName="System.Data.SqlClient" /> --> + </connectionStrings> + <appSettings> + <!-- access these values via the property: + System.Configuration.ConfigurationManager.AppSettings[key] + --> + <add key="connectorserver.port" value="8760"/> + <add key="connectorserver.usessl" value="false"/> + <add key="connectorserver.certificatestorename" value="ConnectorServerSSLCertificate"/> + <add key="connectorserver.ifaddress" value="0.0.0.0"/> + <add key="connectorserver.maxFacadeLifeTime" value="0"/> + <add key="connectorserver.key" value="lmA6bMfENJGlIDbfrVtklXFK32s="/> + <!-- + Enable/Disable the logging proxy for all operations. + --> + <add key="logging.proxy" value="false"/> + </appSettings> + <system.diagnostics> + <trace autoflush="true" indentsize="4"> + <listeners> + <remove name="Default"/> + <add name="console"/> + <add name="file"/> + </listeners> + </trace> + <sources> + <source name="ConnectorServer" switchName="switch1"> + <listeners> + <remove name="Default"/> + <add name="file"/> + </listeners> + </source> + <source name="MyClass2" switchName="switch2"> + <listeners> + <remove name="Default"/> + <add name="file"/> + </listeners> + </source> + </sources> + <switches> + <add name="switch1" value="Information"/> + <add name="switch2" value="Warning"/> + </switches> + <sharedListeners> + <add name="console" type="System.Diagnostics.ConsoleTraceListener"> + </add> + <add name="file" type="System.Diagnostics.TextWriterTraceListener" initializeData="logs\connectorserver.log" traceOutputOptions="DateTime"> + <filter type="System.Diagnostics.EventTypeFilter" initializeData="Information"/> + </add> + </sharedListeners> + </system.diagnostics> + +</configuration> + + \ No newline at end of file diff --git a/dotnet/framework/Service/Service.cs b/dotnet/framework/Service/Service.cs new file mode 100644 index 00000000..96a542b7 --- /dev/null +++ b/dotnet/framework/Service/Service.cs @@ -0,0 +1,185 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Specialized; +using System.Configuration; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.ServiceProcess; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Server; + +namespace Org.IdentityConnectors.Framework.Service +{ + public class Service : ServiceBase + { + private const string PROP_PORT = "connectorserver.port"; + private const string PROP_SSL = "connectorserver.usessl"; + private const string PROP_CERTSTORE = "connectorserver.certificatestorename"; + private const string PROP_IFADDRESS = "connectorserver.ifaddress"; + public const string PROP_KEY = "connectorserver.key"; + public const string PROP_FACADE_LIFETIME = "connectorserver.maxFacadeLifeTime"; + + private ConnectorServer _server; + + public Service() + { + } + + public void DumpRequests() + { + _server.DumpRequests(); + } + + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + } + + private void initializeCurrentDirectory() + { + Assembly assembly = + Assembly.GetExecutingAssembly(); + FileInfo thisAssemblyFile = + new FileInfo(assembly.Location); + DirectoryInfo directory = + thisAssemblyFile.Directory; + Environment.CurrentDirectory = + directory.FullName; + + } + + private NameValueCollection GetApplicationSettings() + { + return ConfigurationManager.AppSettings; + } + + private X509Certificate GetCertificate() + { + NameValueCollection settings = + GetApplicationSettings(); + String storeName = settings.Get(PROP_CERTSTORE); + if (storeName == null) { + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException("Missing required configuration setting: "+PROP_CERTSTORE); + } + + X509Store store = new X509Store(storeName, + StoreLocation.LocalMachine); + + store.Open(OpenFlags.ReadOnly|OpenFlags.OpenExistingOnly); + X509CertificateCollection certificates = store.Certificates; + if ( certificates.Count != 1 ) { + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException("There is supported to be exactly one certificate in the store: "+storeName); + } + X509Certificate certificate = store.Certificates[0]; + store.Close(); + return certificate; + } + + public void StartService(string [] args) + { + OnStart(args); + } + + /// + /// Start this service. + /// + protected override void OnStart(string[] args) + { + try { + initializeCurrentDirectory(); + Trace.TraceInformation("Starting connector server: "+Environment.CurrentDirectory); + NameValueCollection settings = + GetApplicationSettings(); + String portStr = + settings.Get(PROP_PORT); + if ( portStr == null ) { + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException("Missing required configuration property: "+PROP_PORT); + } + String keyHash = settings.Get(PROP_KEY); + if ( keyHash == null ) { + throw new Org.IdentityConnectors.Framework.Common.Exceptions.ConfigurationException("Missing required configuration property: "+PROP_KEY); + } + + int port = Int32.Parse(portStr); + bool useSSL = Boolean.Parse(settings.Get(PROP_SSL)??"false"); + _server = ConnectorServer.NewInstance(); + _server.Port = port; + _server.UseSSL = useSSL; + _server.KeyHash = keyHash; + if (useSSL) { + _server.ServerCertificate = + GetCertificate(); + } + String ifaddress = settings.Get(PROP_IFADDRESS); + if ( ifaddress != null ) { + _server.IfAddress = + IOUtil.GetIPAddress(ifaddress); + } + String facedeLifeTimeStr = settings.Get(PROP_FACADE_LIFETIME); + if (facedeLifeTimeStr != null) + { + _server.MaxFacadeLifeTime = Int32.Parse(facedeLifeTimeStr); + } + _server.Start(); + Trace.TraceInformation("Started connector server"); + } + catch (Exception e) { + TraceUtil.TraceException("Exception occured starting connector server", + e); + throw; + } + } + + public void StopService() + { + OnStop(); + } + + + /// + /// Stop this service. + /// + protected override void OnStop() + { + try { + Trace.TraceInformation("Stopping connector server"); + if (_server != null) { + _server.Stop(); + } + Trace.TraceInformation("Stopped connector server"); + } + catch (Exception e) { + TraceUtil.TraceException("Exception occured stopping connector server", + e); + } + } + } +} diff --git a/dotnet/framework/Service/Service.csproj b/dotnet/framework/Service/Service.csproj new file mode 100644 index 00000000..56504037 --- /dev/null +++ b/dotnet/framework/Service/Service.csproj @@ -0,0 +1,140 @@ + + + + + {A9D6374A-D51F-4FA3-8C02-5B1D23FAA82E} + Debug + AnyCPU + Exe + Org.IdentityConnectors.Framework.Service + ConnectorServer + Open Connectors Framework - Connector Server Service + v4.5.2 + False + false + + + + prompt + ./bin/Debug/ + true + Full + False + True + DEBUG;TRACE + Exe + ConnectorServer + False + 4 + false + + + ./bin/Release/ + true + pdbonly + True + False + TRACE + prompt + Exe + ConnectorServer + False + 4 + false + + + False + Auto + 4194304 + AnyCPU + 4096 + + + FR_ICF_sq_med.ico + + + + + + + True + + + 4.0 + + + + True + + + 4.0 + + + + + + + + Component + + + True + True + Resources.resx + + + Component + + + Designer + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + {5B011775-B121-4EEE-A410-BA2D2F5BFB8B} + FrameworkInternal + + + {8B24461B-456A-4032-89A1-CD418F7B5B62} + Framework + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/Service/app.config b/dotnet/framework/Service/app.config new file mode 100644 index 00000000..d2273dae --- /dev/null +++ b/dotnet/framework/Service/app.config @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/framework/Service/version.template b/dotnet/framework/Service/version.template new file mode 100644 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/Service/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/ServiceInstall/ExtBuild.proj b/dotnet/framework/ServiceInstall/ExtBuild.proj new file mode 100644 index 00000000..932d44f2 --- /dev/null +++ b/dotnet/framework/ServiceInstall/ExtBuild.proj @@ -0,0 +1,71 @@ + + + + Debug + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/framework/ServiceInstall/File.bottom b/dotnet/framework/ServiceInstall/File.bottom new file mode 100644 index 00000000..034b63c4 --- /dev/null +++ b/dotnet/framework/ServiceInstall/File.bottom @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dotnet/framework/ServiceInstall/File.top b/dotnet/framework/ServiceInstall/File.top new file mode 100644 index 00000000..5a817260 --- /dev/null +++ b/dotnet/framework/ServiceInstall/File.top @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/ServiceInstall/Localize/UILoc_en-us.wxl b/dotnet/framework/ServiceInstall/Localize/UILoc_en-us.wxl new file mode 100644 index 00000000..5feef838 --- /dev/null +++ b/dotnet/framework/ServiceInstall/Localize/UILoc_en-us.wxl @@ -0,0 +1,29 @@ + + + + Connector Server Settings + Shared Key + TCP Port + Failed to setup configuration. ([2] [3] [4] [5]) + \ No newline at end of file diff --git a/dotnet/framework/ServiceInstall/Resources/FR_ICF_sq_med.ico b/dotnet/framework/ServiceInstall/Resources/FR_ICF_sq_med.ico new file mode 100755 index 00000000..10240f47 Binary files /dev/null and b/dotnet/framework/ServiceInstall/Resources/FR_ICF_sq_med.ico differ diff --git a/dotnet/framework/ServiceInstall/Resources/SetupBanner.bmp b/dotnet/framework/ServiceInstall/Resources/SetupBanner.bmp new file mode 100644 index 00000000..5ba1a37a Binary files /dev/null and b/dotnet/framework/ServiceInstall/Resources/SetupBanner.bmp differ diff --git a/dotnet/framework/ServiceInstall/Resources/product_License.rtf b/dotnet/framework/ServiceInstall/Resources/product_License.rtf new file mode 100644 index 00000000..c46d9f63 --- /dev/null +++ b/dotnet/framework/ServiceInstall/Resources/product_License.rtf @@ -0,0 +1,309 @@ +{\rtf1\ansi\deff3\adeflang1025 +{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f5\froman\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f6\froman\fprq2\fcharset0 Courier{\*\falt Courier New};}{\f7\fswiss\fprq2\fcharset128 Tahoma;}{\f8\fnil\fprq2\fcharset0 Lucida Sans;}{\f9\fnil\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}} +{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;} +{\stylesheet{\s0\snext0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057 Normal;} +{\s1\sbasedon15\snext1\ql\nowidctlpar\hyphpar0\sb240\sa120\keepn\ltrpar\cf1\b\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f4\fs36\lang2057 Heading 1;} +{\s2\sbasedon15\snext2\ql\nowidctlpar\hyphpar0\sb200\sa120\keepn\ltrpar\cf1\b\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f4\fs32\lang2057 Heading 2;} +{\s3\sbasedon15\snext3\ql\nowidctlpar\hyphpar0\sb140\sa120\keepn\ltrpar\cf1\b\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f4\fs28\lang2057 Heading 3;} +{\s15\sbasedon0\snext16\ql\nowidctlpar\hyphpar0\sb240\sa120\keepn\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f5\fs28\lang2057 Heading;} +{\s16\sbasedon0\snext16\sl288\slmult1\ql\nowidctlpar\hyphpar0\sb0\sa140\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f3\fs24\lang2057 Text Body;} +{\s17\sbasedon16\snext17\sl288\slmult1\ql\nowidctlpar\hyphpar0\sb0\sa140\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f3\fs24\lang2057 List;} +{\s18\sbasedon0\snext18\ql\nowidctlpar\hyphpar0\sb120\sa120\ltrpar\cf1\i\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f3\fs24\lang2057 Caption;} +{\s19\sbasedon0\snext19\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f3\fs24\lang2057 Index;} +{\s20\sbasedon0\snext20\ql\nowidctlpar\hyphpar0\li567\ri567\lin567\rin567\fi0\sb0\sa283\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f3\fs24\lang2057 Quotations;} +{\s21\sbasedon15\snext21\qc\nowidctlpar\hyphpar0\sb240\sa120\keepn\ltrpar\cf1\b\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f5\fs56\lang2057 Title;} +{\s22\sbasedon15\snext22\qc\nowidctlpar\hyphpar0\sb60\sa120\keepn\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\loch\f5\fs36\lang2057 Subtitle;} +}{\*\generator LibreOffice/5.0.0.5$MacOSX_X86_64 LibreOffice_project/1b1a90865e348b492231e1c451437d7a15bb262b}{\info{\creatim\yr2015\mo8\dy15\hr18\min24}{\revtim\yr2015\mo8\dy17\hr16\min23}{\printim\yr0\mo0\dy0\hr0\min0}}\deftab720 +\hyphauto0\viewscale80 +{\*\pgdsctbl +{\pgdsc0\pgdscuse451\pgwsxn11906\pghsxn16838\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Default Style;}} +\formshade{\*\pgdscno0}\paperh16838\paperw11906\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\sectunlocked1\pgndec\pgwsxn11906\pghsxn16838\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc +{\*\ftnsep\chftnsep}\pgndec\pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\b\kerning0\afs12\ab\rtlch \ltrch\loch\fs12\loch\f7 +READ THIS SOFTWARE LICENSE AGREEMENT CAREFULLY. BY DOWNLOADING OR INSTALLING} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\b\kerning0\afs12\ab\rtlch \ltrch\loch\fs12\loch\f7 +THE FORGEROCK SOFTWARE, YOU, ON BEHALF OF YOURSELF AND YOUR COMPANY, AGREE TO} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\b\kerning0\afs12\ab\rtlch \ltrch\loch\fs12\loch\f7 +BE BOUND BY THIS SOFTWARE LICENSE AGREEMENT. IF YOU DO NOT AGREE TO THESE} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\b\kerning0\afs12\ab\rtlch \ltrch\loch\fs12\loch\f7 +TERMS, DO NOT DOWNLOAD OR INSTALL THE FORGEROCK SOFTWARE.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +1. Software License.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +1.1. Development Right to Use. If Company intends to or does use the ForgeRock} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Software only for the purpose(s) of developing, testing, prototyping and} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +demonstrating its application software, then ForgeRock hereby grants Company a} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +nonexclusive, nontransferable, limited license to use the ForgeRock Software} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +only for those purposes, solely at Company's facilities and only in a} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +non-production environment. ForgeRock may audit Company's use of the ForgeRock} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Software to confirm that a production license is not required upon reasonable} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +written notice to Company. If Company intends to use the ForgeRock Software in} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +a live environment, Company must purchase a production license and may only use} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +the ForgeRock Software licensed thereunder in accordance with the terms and} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +conditions of that subscription agreement.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +1.2. Restrictions. Except as expressly set forth in this ForgeRock Software} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +License Agreement (the "Agreement"), Company shall not, directly or indirectly:} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +(a) sublicense, resell, rent, lease, distribute or otherwise transfer rights or} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +usage in the ForgeRock Software, including without limitation to Company} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +subsidiaries and affiliates; (b) remove or alter any copyright, trademark or} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +proprietary notices in the ForgeRock Software; or (c) use the ForgeRock} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Software in any way that would subject the ForgeRock Software, in whole in or} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +in part, to a Copyleft License. As used herein, "Copyleft License" means a} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +software license that requires that information necessary for reproducing and} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +modifying such software must be made available publicly to recipients of} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +executable versions of such software (see, e.g., GNU General Public License and} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +http://www.gnu.org/copyleft/).} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +2. Proprietary Rights.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +2.1. ForgeRock Intellectual Property. Title to and ownership of all copies of} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +the ForgeRock Software whether in machine-readable (source, object code or} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +other format) or printed form, and all related technical know-how and all} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +rights therein (including without limitation all intellectual property rights} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +applicable thereto), belong to ForgeRock and its licensors and shall remain the} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +exclusive property thereof. ForgeRock's name, logo, trade names and trademarks} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +are owned exclusively by ForgeRock and no right is granted to Company to use} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +any of the foregoing except as expressly permitted herein. All rights not} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +expressly granted to Company are reserved by ForgeRock and its licensors.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +2.2. Suggestions. Company hereby grants to ForgeRock a royalty-free, worldwide,} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +transferable, sublicensable and irrevocable right and license to use, copy,} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +modify and distribute, including by incorporating into any product or service} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +owned by ForgeRock, any suggestions, enhancements, recommendations or other} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +feedback provided by Company relating to any product or service owned or} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +offered by ForgeRock.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +2.3. Source Code. The source code underlying the ForgeRock Software is} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +available at www.forgerock.org.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +3. Term and Termination. The terms of this Agreement shall commence on the} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Effective Date and shall continue in force unless earlier terminated in} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +accordance this Section. This Agreement shall terminate without notice to} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Company in the event Company is in material breach of any of the terms and} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +conditions of this Agreement. As used herein, "Effective Date" means the date} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +on which Company first accepted this Agreement and downloads the ForgeRock} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Software.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +4. Disclaimer of Warranties. THE FORGEROCK SOFTWARE LICENSED HEREUNDER IS} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +LICENSED "AS IS" AND WITHOUT WARRANTY OF ANY KIND. FORGEROCK AND IT'S LICENSORS} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +EXPRESSLY DISCLAIM ALL WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY,} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY,} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +FITNESS FOR A PARTICULAR PURPOSE AND ANY WARRANTY OF NON-INFRINGEMENT.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +5. General Indemnification. Company shall defend, indemnify and hold ForgeRock} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +harmless from and against any and all liabilities, damages, losses, costs and} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +expenses (including but not limited to reasonable fees of attorneys and other} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +professionals) payable to third parties based upon any claim arising out of or} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +related to the use of Company's products, provided that ForgeRock: (a) promptly} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +notifies Company of the claim; (b) provides Company with all reasonable} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +information and assistance, at Company's expense, to defend or settle such a} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +claim; and (c) grants Company authority and control of the defense or} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +settlement of such claim. Company shall not settle any such claim, without} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +ForgeRock's prior written consent, if such settlement would in any manner} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +effect ForgeRock's rights in the ForgeRock Software or otherwise. ForgeRock} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +reserves the right to retain counsel, at ForgeRock's expense, to participate in} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +the defense and settlement of any such claim.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +6. Limitation of Liability. IN NO EVENT SHALL FORGEROCK BE LIABLE FOR THE COST} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, ANY LOST PROFITS, REVENUE, OR} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +DATA, INTERRUPTION OF BUSINESS OR FOR ANY INCIDENTAL, SPECIAL, CONSEQUENTIAL OR} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +INDIRECT DAMAGES OF ANY KIND, AND WHETHER ARISING OUT OF BREACH OF WARRANTY,} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE,} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE OR IF SUCH DAMAGE COULD HAVE} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +BEEN REASONABLY FORESEEN. IN NO EVENT SHALL FORGEROCK'S LIABILITY ARISING OUT} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +OF OR RELATED TO THIS AGREEMENT WHETHER IN CONTRACT, TORT OR UNDER ANY OTHER} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +THEORY OF LIABILITY, EXCEED IN THE AGGREGATE $1,000 USD.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +7. General.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +7.1. Governing Law. This Agreement shall be governed by and interpreted in} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +accordance with the laws of the State of California without reference to its} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +conflicts of law provisions.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +7.2. Assignment. Company may not assign any of its rights or obligations under} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +this Agreement without the prior written consent of ForgeRock, which consent} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +shall not be unreasonably withheld. Any assignment not in conformity with this} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Section shall be null and void.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +7.3. Waiver. A waiver on one occasion shall not be construed as a waiver of any} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +right on any future occasion. No delay or omission by a party in exercising any} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +of its rights hereunder shall operate as a waiver of such rights.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +7.4. Compliance with Law. The ForgeRock Software is subject to U.S. export} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +control laws, including the U.S. Export Administration Act and its associated} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +regulations, and may be subject to export or import regulations in other} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +countries. Company agrees to comply with all laws and regulations of the United} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +States and other countries ("Export Laws") to assure that neither the ForgeRock} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Software, nor any direct products thereof are; (a) exported, directly or} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +indirectly, in violation of Export Laws, either to any countries that are} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +subject to U.S. export restrictions or to any end user who has been prohibited} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +from participating in the U.S. export transactions by any federal agency of the} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +U.S. government or (b) intended to be used for any purpose prohibited by Export} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Laws, including, without limitation, nuclear, chemical, or biological weapons} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +proliferation.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +7.5. US Government Restrictions. Company acknowledges that the ForgeRock} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Software consists of "commercial computer software" and "commercial computer} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +software documentation" as such terms are defined in the Code of Federal} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +Regulations. No Government procurement regulations or contract clauses or} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +provisions shall be deemed a part of any transaction between the parties unless} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +its inclusion is required by law, or mutually agreed in writing by the parties} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +in connection with a specific transaction. Use, duplication, reproduction,} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +release, modification, disclosure or transfer of the ForgeRock Software is} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +restricted in accordance with the terms of this Agreement.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +7.6. Provision Severability. In the event that it is determined by a court of} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +competent jurisdiction that any provision of this Agreement is invalid,} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +illegal, or otherwise unenforceable, such provision shall be enforced as nearly} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +as possible in accordance with the stated intention of the parties, while the} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +remainder of this Agreement shall remain in full force and effect and bind the} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +parties according to its terms. To the extent any provision cannot be enforced} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +in accordance with the stated intentions of the parties, such terms and} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +conditions shall be deemed not to be a part of this Agreement.} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +7.7. Entire Agreement. This Agreement constitutes the entire and exclusive} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +agreement between the parties with respect to the subject matter hereof and} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +supersede any prior agreements between the parties with respect to such subject} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0{\outl0\expnd0\expndtw0\kerning0\afs16\rtlch \ltrch\loch\fs16\loch\f7 +matter} +\par \pard\plain \s0\ql\nowidctlpar\hyphpar0\ltrpar\cf1\kerning1\dbch\af8\langfe1081\dbch\af9\afs24\alang1081\loch\f3\fs24\lang2057\nowidctlpar\hyphpar0\afs16\rtlch \ltrch\loch\fs16\loch\f7 + +\par } \ No newline at end of file diff --git a/dotnet/framework/ServiceInstall/Resources/setupDialog.bmp b/dotnet/framework/ServiceInstall/Resources/setupDialog.bmp new file mode 100644 index 00000000..506ba8bf Binary files /dev/null and b/dotnet/framework/ServiceInstall/Resources/setupDialog.bmp differ diff --git a/dotnet/framework/ServiceInstall/ServiceInstall.wixproj b/dotnet/framework/ServiceInstall/ServiceInstall.wixproj new file mode 100644 index 00000000..c83a85cb --- /dev/null +++ b/dotnet/framework/ServiceInstall/ServiceInstall.wixproj @@ -0,0 +1,146 @@ + + + + + {1CBA8F74-050C-432B-8437-08BD13BDC684} + Debug + AnyCPU + Package + ServiceInstall + ServiceInstall + $(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets + $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets + en-US + ICE45 + 1.5.0.0-SNAPSHOT + + + prompt + 4 + AnyCPU + bin\Debug\ + True + Full + False + True + DEBUG;TRACE + + + pdbonly + bin\Release\ + TRACE + prompt + 4 + AnyCPU + true + True + False + 1.5.0.0 + + + + + + $(WixExtDir)\WixUtilExtension.dll + WixUtilExtension + + + $(WixExtDir)\WixUIExtension.dll + WixUIExtension + + + $(WixExtDir)\WixNetFxExtension.dll + WixNetFxExtension + + + + + + + + + + + + + + + + + + + + + + Common + {f140e8da-52b4-4159-992a-9da10ea8eefb} + True + True + + + ConnectorServerService + {e5dcc07f-7b42-4ae9-8d6c-a15525476e0a} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + FrameworkInternal + {5b011775-b121-4eee-a410-ba2d2f5bfb8b} + True + True + + + Framework + {8b24461b-456a-4032-89a1-cd418f7b5b62} + True + True + + + PowerShellScriptExecutorFactory + {57754ffa-bb1f-4722-a2fa-70c4f27c6784} + True + True + + + Service + {a9d6374a-d51f-4fa3-8c02-5b1d23faa82e} + True + True + + + ShellScriptExecutorFactory + {4700690a-2d29-40a0-86ac-e5a9f71a479a} + True + True + + + TestCommon + {e6a207d2-e083-41bf-b522-d9d3ec09323e} + True + True + + + \ No newline at end of file diff --git a/dotnet/framework/ServiceInstall/SettingsDlg.wxs b/dotnet/framework/ServiceInstall/SettingsDlg.wxs new file mode 100755 index 00000000..141d0cb3 --- /dev/null +++ b/dotnet/framework/ServiceInstall/SettingsDlg.wxs @@ -0,0 +1,95 @@ + + + + + + + + Please enter your service configuration + + + {\WixUI_Font_Title}!(loc.SettingsDlg_Title) + + + + + + + + + + + + + Service Port: + + + + + + + + + 1 + + + + + 1 + + + 1 + CostingComplete = 1 + ProductID + + + 1 + + + + + + + + + [SourceDir]ConnectorServerService.exe + + + + NOT Installed + + + diff --git a/dotnet/framework/ServiceInstall/Setup.wxs b/dotnet/framework/ServiceInstall/Setup.wxs new file mode 100644 index 00000000..d2d532a3 --- /dev/null +++ b/dotnet/framework/ServiceInstall/Setup.wxs @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OpenICF Remote Connector Server + Visit us at https://www.forgerock.com/contact/ + https://www.forgerock.com/services/support-services/ + +44-1935-804797 + https://forgerock.org/openicf/ + https://forgerock.org/openicf/ + + + + + + + + + + + + + + + + + + + + 8759 + 8760 + + diff --git a/dotnet/framework/ShellScriptExecutorFactory/ShellScriptExecutorFactory.cs b/dotnet/framework/ShellScriptExecutorFactory/ShellScriptExecutorFactory.cs new file mode 100644 index 00000000..19a66798 --- /dev/null +++ b/dotnet/framework/ShellScriptExecutorFactory/ShellScriptExecutorFactory.cs @@ -0,0 +1,221 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2014 ForgeRock AS + */ +using System; +using System.IO; +using System.Security; +using System.Diagnostics; +using System.Reflection; +using System.Collections.Generic; +using Org.IdentityConnectors.Common.Security; +using System.Threading; + +namespace Org.IdentityConnectors.Common.Script.Shell +{ + + /// + /// Process shell scripts. Valid arguments are below, the rest will be + /// used as environment variables. The return is the exit code of the + /// process. + ///
    + ///
  • USERNAME - name of the user to run this script as..
  • + ///
  • PASSWORD - (GuardedString or SecureString) password for the user to run this script as..
  • + ///
  • WORKINGDIR - working directory run this script in..
  • + ///
  • TIMEOUT - timeout waiting for script to finish in ms (default: 30 secs)
  • + ///
+ ///
+ [ScriptExecutorFactoryClass("Shell")] + public class ShellScriptExecutorFactory : ScriptExecutorFactory + { + + /// + /// Creates a script executor give the Shell script. + /// + override + public ScriptExecutor NewScriptExecutor(Assembly[] refs, string script, bool compile) + { + return new ShellScriptExecutor(script); + } + + /// + /// Processes the script. + /// + class ShellScriptExecutor : ScriptExecutor + { + private readonly string _script; + + public ShellScriptExecutor(string script) + { + _script = script; + } + public object Execute(IDictionary arguments) + { + // + string fn = String.Empty; + // create the process info.. + Process process = new Process(); + // set the defaults.. + process.StartInfo.CreateNoWindow = true; + process.StartInfo.UseShellExecute = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + // set the default timeout.. + int timeout = 1000 * 30; // 30 secss + IDictionary result = new Dictionary(); + int exitCode = 1; + try + { + Trace.TraceInformation("About to execute script: {0}", _script); + // if there are any environment varibles set to false.. + process.StartInfo.UseShellExecute = false;//arguments.Count == 0; + // take out username and password if they're in the options. + foreach (KeyValuePair kv in arguments) + { + if (kv.Key.ToUpper().Equals("USERNAME")) + { + string domainUser = kv.Value.ToString(); + string[] split = domainUser.Split(new char[] { '\\' }); + if (split.Length == 1) + { + process.StartInfo.UserName = split[0]; + } + else + { + process.StartInfo.Domain = split[0]; + process.StartInfo.UserName = split[1]; + } + } + else if (kv.Key.ToUpper().Equals("PASSWORD")) + { + if (kv.Value is SecureString) + { + process.StartInfo.Password = (SecureString)kv.Value; + } + else if (kv.Value is GuardedString) + { + process.StartInfo.Password = ((GuardedString)kv.Value).ToSecureString(); + } + else + { + throw new ArgumentException("Invalid type for password."); + } + } + else if (kv.Key.ToUpper().Equals("WORKINGDIR")) + { + process.StartInfo.WorkingDirectory = kv.Value.ToString(); + } + else if (kv.Key.ToUpper().Equals("TIMEOUT")) + { + timeout = Int32.Parse(kv.Value.ToString()); + } + else + { + if (kv.Value == null) + { + Trace.TraceWarning("...parameter {0} has null value, skipping it", kv.Key); + } + else + { + Trace.TraceInformation("...with parameter {0} set to {1}", kv.Key, kv.Value); + process.StartInfo.EnvironmentVariables[kv.Key] = kv.Value.ToString(); + } + } + } + // write out the script.. + fn = Path.GetTempFileName() + ".cmd"; + StreamWriter sw = null; + try + { + sw = new StreamWriter(fn); + sw.Write(_script); + } + finally + { + sw.Close(); + sw.Dispose(); + } + // set temp file.. + process.StartInfo.FileName = fn; + // execute script.. + process.Start(); + string stdout = process.StandardOutput.ReadToEnd(); + Trace.TraceInformation("execution started; stdout = {0}", stdout); // this is quite suspicious... + // http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx + // Use asynchronous read operations on at least one of the streams. + AsynchronousReader msr_stderr = new AsynchronousReader(process.StandardError); + // Create the thread objects to run the code asynchronously + Thread t_stderr = new Thread(msr_stderr.Go); + t_stderr.Start(); + t_stderr.Join(); + // wait for the process to exit.. + if (!process.WaitForExit(timeout)) + { + throw new TimeoutException("Script failed to exit in time!"); + } + exitCode = process.ExitCode; + result.Add("stdout", stdout); + result.Add("stderr", msr_stderr.Text); + Trace.TraceInformation("execution finished; stderr = {0}", msr_stderr.Text); + } + catch (Exception e) + { + Trace.TraceError("Failed to execute script with exception {0}", e.Message); + } + finally + { + // close up the process + process.Close(); + process.Dispose(); + } + // clean up temp file.. + try + { + File.Delete(fn); + } + catch (Exception) + { + + } + Trace.TraceInformation("exitCode = {0}", exitCode); + result.Add("exitCode", exitCode); + return result; + } + } + } + internal class AsynchronousReader + { + StreamReader _sr = null; + string _text = null; + public string Text { get { return _text; } } + + public AsynchronousReader(StreamReader sr) + { + _sr = sr; + } + + public void Go() + { + _text = _sr.ReadToEnd(); + } + } +} diff --git a/dotnet/framework/ShellScriptExecutorFactory/ShellScriptExecutorFactory.csproj b/dotnet/framework/ShellScriptExecutorFactory/ShellScriptExecutorFactory.csproj new file mode 100644 index 00000000..47cd1591 --- /dev/null +++ b/dotnet/framework/ShellScriptExecutorFactory/ShellScriptExecutorFactory.csproj @@ -0,0 +1,86 @@ + + + + + {4700690A-2D29-40A0-86AC-E5A9F71A479A} + Debug + AnyCPU + Library + Sun.OpenConnectors.Common.Script.Shell + Shell.ScriptExecutorFactory + ShellScriptExecutorFactory + v4.5.2 + true + + + + prompt + 4 + AnyCPU + bin\Debug\ + True + Full + False + True + DEBUG;TRACE + false + + + pdbonly + bin\Release\ + prompt + true + 4 + AnyCPU + True + False + TRACE + false + + + + + + 4.0 + + + + 4.0 + + + + + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/ShellScriptExecutorFactory/version.template b/dotnet/framework/ShellScriptExecutorFactory/version.template new file mode 100644 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/ShellScriptExecutorFactory/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/TestBundles/TestBundleV1/AssemblyInfo.cs b/dotnet/framework/TestBundles/TestBundleV1/AssemblyInfo.cs new file mode 100644 index 00000000..968dd3be --- /dev/null +++ b/dotnet/framework/TestBundles/TestBundleV1/AssemblyInfo.cs @@ -0,0 +1,56 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +#region Using directives + +using System.Reflection; +using System.Runtime.InteropServices; +using System.Resources; + +#endregion + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TestBundleV1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestBundleV1")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// This sets the default COM visibility of types in the assembly to invisible. +// If you need to expose a type to COM, use [ComVisible(true)] on that type. +[assembly: ComVisible(false)] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all the values or you can use the default the Revision and +// Build Numbers by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: NeutralResourcesLanguage("en-US")] + diff --git a/dotnet/framework/TestBundles/TestBundleV1/Messages.es-ES.resx b/dotnet/framework/TestBundles/TestBundleV1/Messages.es-ES.resx new file mode 100644 index 00000000..912c5950 --- /dev/null +++ b/dotnet/framework/TestBundles/TestBundleV1/Messages.es-ES.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tstField.display_es-ES + + + tstField.help_es-ES + + + tstField.group_es-ES + + \ No newline at end of file diff --git a/dotnet/framework/TestBundles/TestBundleV1/Messages.es.resx b/dotnet/framework/TestBundles/TestBundleV1/Messages.es.resx new file mode 100644 index 00000000..600a2b43 --- /dev/null +++ b/dotnet/framework/TestBundles/TestBundleV1/Messages.es.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tstField.help_es + + + tstField.display_es + + + tstField.group_es + + \ No newline at end of file diff --git a/dotnet/framework/TestBundles/TestBundleV1/Messages.resx b/dotnet/framework/TestBundles/TestBundleV1/Messages.resx new file mode 100644 index 00000000..9742ebb9 --- /dev/null +++ b/dotnet/framework/TestBundles/TestBundleV1/Messages.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Help for test field. + + + Display for test field. + + + Group for test field. + + \ No newline at end of file diff --git a/dotnet/framework/TestBundles/TestBundleV1/TestBundleV1.csproj b/dotnet/framework/TestBundles/TestBundleV1/TestBundleV1.csproj new file mode 100644 index 00000000..1a0e561d --- /dev/null +++ b/dotnet/framework/TestBundles/TestBundleV1/TestBundleV1.csproj @@ -0,0 +1,94 @@ + + + + + {0BC2A013-56FE-46DD-90FC-2D2D57748FB6} + Debug + AnyCPU + Library + TestBundleV1 + TestBundleV1.Connector + v4.5.2 + OnBuildSuccess + + + + prompt + 4 + AnyCPU + bin\Debug\ + True + Full + False + True + DEBUG;TRACE + false + + + pdbonly + bin\Release\ + TRACE + prompt + 4 + AnyCPU + False + True + False + false + + + + + + + + + + + + + + Designer + + + Designer + + + Designer + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + {8B24461B-456A-4032-89A1-CD418F7B5B62} + Framework + + + + + + \ No newline at end of file diff --git a/dotnet/framework/TestBundles/TestBundleV1/TestConnector.cs b/dotnet/framework/TestBundles/TestBundleV1/TestConnector.cs new file mode 100755 index 00000000..552cd3e9 --- /dev/null +++ b/dotnet/framework/TestBundles/TestBundleV1/TestConnector.cs @@ -0,0 +1,1304 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2015 ForgeRock AS. + */ + +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Script; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Exceptions; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; +using ICF = Org.IdentityConnectors.Framework.Common.Objects; + +namespace org.identityconnectors.testconnector +{ + + #region MyTstConnection + + public class MyTstConnection + { + private readonly int _connectionNumber; + private bool _isGood = true; + + public MyTstConnection(int connectionNumber) + { + _connectionNumber = connectionNumber; + } + + public void Test() + { + if (!_isGood) + { + throw new ConnectorException("Connection is bad"); + } + } + + public void Dispose() + { + _isGood = false; + } + + public bool IsGood() + { + return _isGood; + } + + public int GetConnectionNumber() + { + return _connectionNumber; + } + } + + #endregion + + #region TstAbstractConnector + + public abstract class TstAbstractConnector : AuthenticateOp, IConnectorEventSubscriptionOp, + CreateOp, DeleteOp, ResolveUsernameOp, SchemaOp, ScriptOnResourceOp, SearchOp, + ISyncEventSubscriptionOp, SyncOp, TestOp, UpdateOp + { + internal sealed class ResourceComparator : IComparer + { + private readonly IList sortKeys; + + public ResourceComparator(ICF.SortKey[] sortKeys) + { + this.sortKeys = sortKeys; + } + + + public int Compare(ICF.ConnectorObject r1, ICF.ConnectorObject r2) + { + foreach (ICF.SortKey sortKey in sortKeys) + { + int result = Compare(r1, r2, sortKey); + if (result != 0) + { + return result; + } + } + return 0; + } + + private int Compare(ICF.ConnectorObject r1, ICF.ConnectorObject r2, ICF.SortKey sortKey) + { + IList vs1 = ValuesSorted(r1, sortKey.Field); + IList vs2 = ValuesSorted(r2, sortKey.Field); + if (vs1.Count == 0 && vs2.Count == 0) + { + return 0; + } + else if (vs1.Count == 0) + { + // Sort resources with missing attributes last. + return 1; + } + else if (vs2.Count == 0) + { + // Sort resources with missing attributes last. + return -1; + } + else + { + object v1 = vs1[0]; + object v2 = vs2[0]; + return sortKey.IsAscendingOrder() ? CompareValues(v1, v2) : -CompareValues(v1, v2); + } + } + + private IList ValuesSorted(ICF.ConnectorObject resource, string field) + { + ICF.ConnectorAttribute value = resource.GetAttributeByName(field); + if (value == null || value.Value == null || value.Value.Count == 0) + { + return new List(); + } + else if (value.Value.Count > 1) + { + List results = new List(value.Value); + results.Sort(VALUE_COMPARATOR); + return results; + } + else + { + return value.Value; + } + } + } + + private static readonly IComparer VALUE_COMPARATOR = new ComparatorAnonymousInnerClassHelper(); + + private class ComparatorAnonymousInnerClassHelper : IComparer + { + public ComparatorAnonymousInnerClassHelper() + { + } + + public virtual int Compare(object o1, object o2) + { + return CompareValues(o1, o2); + } + } + + private static int CompareValues(object v1, object v2) + { + if (v1 is string && v2 is string) + { + string s1 = (string)v1; + string s2 = (string)v2; + return StringComparer.OrdinalIgnoreCase.Compare(s1, s2); + } + else if (v1 is double && v2 is double) + { + double n1 = (double)v1; + double n2 = (double)v2; + return n1.CompareTo(n2); + } + else if (v1 is int && v2 is int) + { + int n1 = (int)v1; + int n2 = (int)v2; + return n1.CompareTo(n2); + } + else if (v1 is bool && v2 is bool) + { + bool b1 = (bool)v1; + bool b2 = (bool)v2; + return b1.CompareTo(b2); + } + else + { + return v1.GetType().FullName.CompareTo(v2.GetType().FullName); + } + } + + protected TstStatefulConnectorConfig _config; + + public void Init(Configuration cfg) + { + _config = (TstStatefulConnectorConfig)cfg; + Guid g = _config.Guid; + } + + public void Update() + { + _config.UpdateTest(); + } + + public virtual ICF.Uid Authenticate(ICF.ObjectClass objectClass, string username, GuardedString password, + ICF.OperationOptions options) + { + if (_config.returnNullTest) + { + return null; + } + else + { + return _config.Authenticate(objectClass, username, password); + } + } + + public ICF.ISubscription Subscribe(ICF.ObjectClass objectClass, Filter eventFilter, + IObserver handler, ICF.OperationOptions operationOptions) + { + ICF.ConnectorObjectBuilder builder = new ICF.ConnectorObjectBuilder { ObjectClass = objectClass }; + + Object op = CollectionUtil.GetValue(operationOptions.Options, "eventCount", null); + int? eventCount = op as int? ?? 10; + + bool doComplete = operationOptions.Options.ContainsKey("doComplete"); + + ICF.CancellationSubscription subscription = new ICF.CancellationSubscription(); + + DoPeriodicWorkAsync(runCount => + { + if (_config == null) + { + handler.OnError(new InvalidOperationException("Connector has been disposed")); + return false; + } + builder.SetUid(Convert.ToString(runCount)); + builder.SetName(Convert.ToString(runCount)); + handler.OnNext(builder.Build()); + + if (runCount >= eventCount) + { + // Locally stop serving subscription + if (doComplete) + { + handler.OnCompleted(); + } + else + { + handler.OnError(new ConnectorException("Subscription channel is closed")); + } + // The loop should be stopped from here. + return false; + } + return true; + }, new TimeSpan(0, 0, 0, 0, 500), new TimeSpan(0, 0, 1), subscription.Token).ConfigureAwait(false); + + + return subscription; + } + + private async Task DoPeriodicWorkAsync(Func action, + TimeSpan interval, TimeSpan dueTime, CancellationToken token) + { + // Initial wait time before we begin the periodic loop. + await Task.Delay(dueTime, token); + + Int32 i = 0; + // Repeat this loop until cancelled. + while (!token.IsCancellationRequested) + { + if (action(++i)) + { + // Wait to repeat again. + await Task.Delay(interval, token); + } + else + { + break; + } + } + } + + public ICF.ISubscription Subscribe(ICF.ObjectClass objectClass, ICF.SyncToken token, + IObserver handler, ICF.OperationOptions operationOptions) + { + var coBuilder = new ICF.ConnectorObjectBuilder() { ObjectClass = objectClass }; + coBuilder.SetUid("0"); + coBuilder.SetName("SYNC_EVENT"); + + ICF.SyncDeltaBuilder builder = new ICF.SyncDeltaBuilder() + { + DeltaType = ICF.SyncDeltaType.CREATE_OR_UPDATE, + Object = coBuilder.Build() + }; + + Object op = CollectionUtil.GetValue(operationOptions.Options, "eventCount", null); + int? eventCount = op as int? ?? 10; + + bool doComplete = operationOptions.Options.ContainsKey("doComplete"); + + ICF.CancellationSubscription subscription = new ICF.CancellationSubscription(); + + DoPeriodicWorkAsync(runCount => + { + if (_config == null) + { + handler.OnError(new InvalidOperationException("Connector has been disposed")); + return false; + } + builder.Token = new ICF.SyncToken(runCount); + handler.OnNext(builder.Build()); + + if (runCount >= eventCount) + { + // Locally stop serving subscription + if (doComplete) + { + handler.OnCompleted(); + } + else + { + handler.OnError(new ConnectorException("Subscription channel is closed")); + } + // ScheduledFuture should be stopped from here. + return false; + } + return true; + }, new TimeSpan(0, 0, 0, 0, 500), new TimeSpan(0, 0, 1), subscription.Token).ConfigureAwait(false); + + + return subscription; + } + + + public ICF.Uid Create(ICF.ObjectClass objectClass, ICollection createAttributes, + ICF.OperationOptions options) + { + ICF.ConnectorAttributesAccessor accessor = new ICF.ConnectorAttributesAccessor(createAttributes); + if (_config.returnNullTest) + { + return null; + } + if (_config.IsTestObjectClass(objectClass)) + { + return _config.GeObjectCache(objectClass).Create(createAttributes); + } + else + { + if (accessor.HasAttribute("fail")) + { + throw new ConnectorException("Test Exception"); + } + else if (accessor.HasAttribute("exist") && accessor.FindBoolean("exist") == true) + { + throw new AlreadyExistsException(accessor.GetName().GetNameValue()); + } + else if (accessor.HasAttribute("emails")) + { + object value = ICF.ConnectorAttributeUtil.GetSingleValue(accessor.Find("emails")); + if (value is IDictionary) + { + return new ICF.Uid((string)((IDictionary)value)["email"]); + } + else + { + throw new InvalidAttributeValueException("Expecting Map"); + } + } + return new ICF.Uid(_config.Guid.ToString()); + } + } + + public void Delete(ICF.ObjectClass objectClass, ICF.Uid uid, ICF.OperationOptions options) + { + if (_config.returnNullTest) + { + return; + } + if (_config.IsTestObjectClass(objectClass)) + { + _config.GeObjectCache(objectClass).Delete(uid); + } + else + { + if (null == uid.Revision) + { + throw new PreconditionRequiredException("Version is required for MVCC"); + } + else if (_config.Guid.ToString().Equals(uid.Revision)) + { + // Delete + String a = _config.Guid.ToString(); + String b = _config.Guid.ToString(); + String c = _config.Guid.ToString(); + } + else + { + throw new PreconditionFailedException("Current version of resource is 0 and not match with: " + + uid.Revision); + } + } + } + + public virtual ICF.Uid ResolveUsername(ICF.ObjectClass objectClass, string username, + ICF.OperationOptions options) + { + if (_config.returnNullTest) + { + return null; + } + else + { + return _config.ResolveByUsername(objectClass, username); + } + } + + + public virtual ICF.Schema Schema() + { + if (_config.returnNullTest) + { + return null; + } + else + { + ICF.SchemaBuilder builder = new ICF.SchemaBuilder(SafeType.ForRawType(GetType())); + foreach (string type in _config.testObjectClass) + { + ICF.ObjectClassInfoBuilder classInfoBuilder = new ICF.ObjectClassInfoBuilder(); + classInfoBuilder.ObjectType = type; + classInfoBuilder.AddAttributeInfo(ICF.OperationalAttributeInfos.PASSWORD); + builder.DefineObjectClass(classInfoBuilder.Build()); + } + return builder.Build(); + } + } + + public virtual object RunScriptOnResource(ICF.ScriptContext request, ICF.OperationOptions options) + { + if (_config.returnNullTest) + { + return null; + } + else + { + try + { + Assembly assembly = GetType().Assembly; + List list = assembly.GetReferencedAssemblies().Select(Assembly.Load).ToList(); + + return + ScriptExecutorFactory.NewInstance(request.ScriptLanguage) + .NewScriptExecutor(list.ToArray(), request.ScriptText, true) + .Execute(request.ScriptArguments); + } + catch (Exception e) + { + throw new ConnectorException(e.Message, e); + } + } + } + + public FilterTranslator CreateFilterTranslator(ICF.ObjectClass objectClass, ICF.OperationOptions options) + { + return new FilterTranslatorAnonymousInnerClassHelper(); + } + + private class FilterTranslatorAnonymousInnerClassHelper : FilterTranslator + { + public FilterTranslatorAnonymousInnerClassHelper() + { + } + + public IList Translate(Filter filter) + { + List filters = new List(1); + filters.Add(filter); + return filters; + } + } + + public void ExecuteQuery(ICF.ObjectClass objectClass, Filter query, ICF.ResultsHandler handler, + ICF.OperationOptions options) + { + ICF.SortKey[] sortKeys = options.SortKeys; + if (null == sortKeys) + { + sortKeys = new ICF.SortKey[] { new ICF.SortKey(ICF.Name.NAME, true) }; + } + + // Rebuild the full result set. + SortedSet resultSet = + new SortedSet(new ResourceComparator(sortKeys)); + if (_config.returnNullTest) + { + return; + } + else if (_config.IsTestObjectClass(objectClass)) + { + Filter filter = FilteredResultsHandlerVisitor.WrapFilter(query, _config.caseIgnore); + foreach (var connectorObject in _config.GeObjectCache(objectClass).GetIterable(filter)) + { + resultSet.Add(connectorObject); + } + } + else + { + if (null != query) + { + foreach (ICF.ConnectorObject co in collection.Values) + { + if (query.Accept(co)) + { + resultSet.Add(co); + } + } + } + else + { + resultSet.UnionWith(collection.Values); + } + } + // Handle the results + if (null != options.PageSize) + { + // Paged Search + string pagedResultsCookie = options.PagedResultsCookie; + string currentPagedResultsCookie = options.PagedResultsCookie; + int? pagedResultsOffset = null != options.PagedResultsOffset + ? Math.Max(0, (int)options.PagedResultsOffset) + : 0; + int? pageSize = options.PageSize; + int index = 0; + int pageStartIndex = null == pagedResultsCookie ? 0 : -1; + int handled = 0; + foreach (ICF.ConnectorObject entry in resultSet) + { + if (pageStartIndex < 0 && pagedResultsCookie.Equals(entry.Name.GetNameValue())) + { + pageStartIndex = index + 1; + } + if (pageStartIndex < 0 || index < pageStartIndex) + { + index++; + continue; + } + if (handled >= pageSize) + { + break; + } + if (index >= pagedResultsOffset + pageStartIndex) + { + if (handler.Handle(entry)) + { + handled++; + currentPagedResultsCookie = entry.Name.GetNameValue(); + } + else + { + break; + } + } + index++; + } + + if (index == resultSet.Count) + { + currentPagedResultsCookie = null; + } + + if (handler is SearchResultsHandler) + { + ((SearchResultsHandler)handler).HandleResult(new ICF.SearchResult(currentPagedResultsCookie, ICF.SearchResult.CountPolicy.EXACT, + resultSet.Count, resultSet.Count - index)); + } + } + else + { + // Normal Search + foreach (ICF.ConnectorObject entry in resultSet) + { + if (!handler.Handle(entry)) + { + break; + } + } + if (handler is SearchResultsHandler) + { + ((SearchResultsHandler)handler).HandleResult(new ICF.SearchResult()); + } + } + } + + public void Sync(ICF.ObjectClass objectClass, ICF.SyncToken token, ICF.SyncResultsHandler handler, + ICF.OperationOptions options) + { + if (_config.returnNullTest) + { + return; + } + if (_config.IsTestObjectClass(objectClass)) + { + foreach (ICF.SyncDelta delta in _config.Sync(objectClass, (int?)token.Value)) + { + if (!handler.Handle(delta)) + { + break; + } + } + if (handler is SyncTokenResultsHandler) + { + ((SyncTokenResultsHandler)handler).HandleResult(new ICF.SyncToken(_config.LatestSyncToken)); + } + } + else + { + if (handler is SyncTokenResultsHandler) + { + ((SyncTokenResultsHandler)handler).HandleResult(GetLatestSyncToken(objectClass)); + } + } + } + + public ICF.SyncToken GetLatestSyncToken(ICF.ObjectClass objectClass) + { + if (_config.returnNullTest) + { + return null; + } + else if (_config.IsTestObjectClass(objectClass)) + { + return new ICF.SyncToken(_config.LatestSyncToken); + } + else + { + return new ICF.SyncToken(_config.Guid.ToString()); + } + } + + public void Test() + { + if (_config.failValidation) + { + throw new ConnectorException("test failed " + CultureInfo.CurrentUICulture.TwoLetterISOLanguageName); + } + } + + public ICF.Uid Update(ICF.ObjectClass objectClass, ICF.Uid uid, + ICollection replaceAttributes, ICF.OperationOptions options) + { + if (_config.returnNullTest) + { + return null; + } + else if (_config.IsTestObjectClass(objectClass)) + { + return _config.GeObjectCache(objectClass).Update(uid, replaceAttributes); + } + else + { + throw new System.NotSupportedException("Object Update is not supported: " + + objectClass.GetObjectClassValue()); + } + } + + private static readonly SortedDictionary collection = + new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); + + static TstAbstractConnector() + { + bool enabled = true; + for (int i = 0; i < 100; i++) + { + ICF.ConnectorObjectBuilder builder = new ICF.ConnectorObjectBuilder(); + builder.SetUid(Convert.ToString(i)); + builder.SetName(string.Format("user{0:D3}", i)); + builder.AddAttribute(ICF.ConnectorAttributeBuilder.BuildEnabled(enabled)); + IDictionary mapAttribute = new Dictionary(); + mapAttribute["email"] = "foo@example.com"; + mapAttribute["primary"] = true; + mapAttribute["usage"] = new List() { "home", "work" }; + builder.AddAttribute("emails", mapAttribute); + ICF.ConnectorObject co = builder.Build(); + collection[co.Name.GetNameValue()] = co; + enabled = !enabled; + } + } + } + + #endregion + + #region TstConnector + + [ConnectorClass("TestConnector", + "TestConnector.category", + typeof(TstConnectorConfig), + MessageCatalogPaths = new String[] { "TestBundleV1.Messages" } + )] + public class TstConnector : CreateOp, PoolableConnector, SchemaOp, SearchOp, SyncOp + { + private static int _connectionCount = 0; + private MyTstConnection _myConnection; + private TstConnectorConfig _config; + + public ICF.Uid Create(ICF.ObjectClass oclass, ICollection attrs, + ICF.OperationOptions options) + { + int? delay = (int?)CollectionUtil.GetValue(options.Options, "delay", null); + if (delay != null) + { + Thread.Sleep((int)delay); + } + + if (options.Options.ContainsKey("testPooling")) + { + return new ICF.Uid(_myConnection.GetConnectionNumber().ToString()); + } + else + { + String version = GetVersion(); + return new ICF.Uid(version); + } + } + + public void Init(Configuration cfg) + { + _config = (TstConnectorConfig)cfg; + if (_config.resetConnectionCount) + { + _connectionCount = 0; + } + _myConnection = new MyTstConnection(_connectionCount++); + } + + public static String GetVersion() + { + return "1.0"; + } + + public void Dispose() + { + if (_myConnection != null) + { + _myConnection.Dispose(); + _myConnection = null; + } + } + + /// + /// Used by the script tests + /// + public String concat(String s1, String s2) + { + return s1 + s2; + } + + /// + /// Used by the script tests + /// + public void Update() + { + _config.UpdateTest(); + } + + public void CheckAlive() + { + _myConnection.Test(); + } + + private class MyTranslator : AbstractFilterTranslator + { + } + + public FilterTranslator CreateFilterTranslator(ICF.ObjectClass oclass, ICF.OperationOptions options) + { + return new MyTranslator(); + } + + public void ExecuteQuery(ICF.ObjectClass oclass, String query, ICF.ResultsHandler handler, + ICF.OperationOptions options) + { + int remaining = _config.numResults; + for (int i = 0; i < _config.numResults; i++) + { + int? delay = (int?)CollectionUtil.GetValue(options.Options, "delay", null); + if (delay != null) + { + Thread.Sleep((int)delay); + } + ICF.ConnectorObjectBuilder builder = + new ICF.ConnectorObjectBuilder(); + builder.SetUid("" + i); + builder.SetName(i.ToString()); + builder.ObjectClass = oclass; + for (int j = 0; j < 50; j++) + { + builder.AddAttribute("myattribute" + j, "myvaluevaluevalue" + j); + } + ICF.ConnectorObject rv = builder.Build(); + if (handler.Handle(rv)) + { + remaining--; + } + else + { + break; + } + } + + if (handler is SearchResultsHandler) + { + ((SearchResultsHandler)handler).HandleResult(new ICF.SearchResult("", remaining)); + } + } + + public void Sync(ICF.ObjectClass objClass, ICF.SyncToken token, + ICF.SyncResultsHandler handler, + ICF.OperationOptions options) + { + int remaining = _config.numResults; + for (int i = 0; i < _config.numResults; i++) + { + ICF.ConnectorObjectBuilder obuilder = + new ICF.ConnectorObjectBuilder(); + obuilder.SetUid(i.ToString()); + obuilder.SetName(i.ToString()); + obuilder.ObjectClass = (objClass); + + ICF.SyncDeltaBuilder builder = + new ICF.SyncDeltaBuilder(); + builder.Object = (obuilder.Build()); + builder.DeltaType = (ICF.SyncDeltaType.CREATE_OR_UPDATE); + builder.Token = (new ICF.SyncToken("mytoken")); + ICF.SyncDelta rv = builder.Build(); + if (handler.Handle(rv)) + { + remaining--; + } + else + { + break; + } + } + if (handler is SyncTokenResultsHandler) + { + ((SyncTokenResultsHandler)handler).HandleResult(new ICF.SyncToken(remaining)); + } + } + + public ICF.SyncToken GetLatestSyncToken(ICF.ObjectClass objectClass) + { + return new ICF.SyncToken("mylatest"); + } + + public ICF.Schema Schema() + { + ICF.SchemaBuilder builder = new ICF.SchemaBuilder(SafeType.Get()); + for (int i = 0; i < 2; i++) + { + ICF.ObjectClassInfoBuilder classBuilder = new ICF.ObjectClassInfoBuilder(); + classBuilder.ObjectType = ("class" + i); + for (int j = 0; j < 200; j++) + { + classBuilder.AddAttributeInfo(ICF.ConnectorAttributeInfoBuilder.Build("attributename" + j, + typeof(String))); + } + builder.DefineObjectClass(classBuilder.Build()); + } + return builder.Build(); + } + } + + #endregion + + #region TstConnectorConfig + + public class TstConnectorConfig : AbstractConfiguration + { + /// + /// keep lower case for consistent unit tests + /// + [ConfigurationProperty(OperationTypes = new Type[] { typeof(SyncOp) })] + public string tstField { get; set; } + + /// + /// keep lower case for consistent unit tests + /// + public int numResults { get; set; } + + /// + /// keep lower case for consistent unit tests + /// + public bool failValidation { get; set; } + + /// + /// keep lower case for consistent unit tests + /// + public bool resetConnectionCount { get; set; } + + public override void Validate() + { + if (failValidation) + { + throw new ConnectorException("validation failed " + + CultureInfo.CurrentUICulture.TwoLetterISOLanguageName); + } + } + + public void UpdateTest() + { + tstField = "change"; + NotifyConfigurationUpdate(); + } + } + + #endregion + + #region TstStatefulConnector + + [ConnectorClass("TestStatefulConnector", + "TestStatefulConnector.category", + typeof(TstStatefulConnectorConfig), + MessageCatalogPaths = new String[] { "TestBundleV1.Messages" } + )] + public class TstStatefulConnector : TstAbstractConnector, Connector + { + //public void Init(Configuration cfg) + //{ + // base.Init(cfg); + //} + + public Configuration Configuration + { + get { return _config; } + } + + public void Dispose() + { + _config = null; + } + } + + #endregion + + #region TstStatefulConnectorConfig + + public class TstStatefulConnectorConfig : TstConnectorConfig, StatefulConfiguration + { + public Boolean caseIgnore { get; set; } + + public String[] testObjectClass { get; set; } + + public Boolean returnNullTest { get; set; } + + public String randomString { get; set; } + + private Guid? guid; + + public Guid Guid + { + get + { + lock (this) + { + if (null == guid) + { + guid = Guid.NewGuid(); + } + return (Guid)guid; + } + } + } + + public void Release() + { + guid = null; + } + + public bool IsTestObjectClass(ICF.ObjectClass objectClass) + { + return null != objectClass && null != testObjectClass && + testObjectClass.Contains(objectClass.GetObjectClassValue(), StringComparer.OrdinalIgnoreCase); + } + + public virtual ICF.Uid ResolveByUsername(ICF.ObjectClass objectClass, string username) + { + ObjectClassCacheEntry cache; + objectCache.TryGetValue(objectClass, out cache); + if (null != cache) + { + ConnectorObjectCacheEntry entry = cache.GetByName(username); + if (null != entry) + { + return entry.ConnectorObject.Uid; + } + } + return null; + } + + public virtual ICF.Uid Authenticate(ICF.ObjectClass objectClass, string username, GuardedString password) + { + ObjectClassCacheEntry cache; + objectCache.TryGetValue(objectClass, out cache); + if (null != cache) + { + ConnectorObjectCacheEntry entry = cache.GetByName(username); + if (null != entry) + { + if (entry.Authenticate(password)) + { + return entry.ConnectorObject.Uid; + } + throw new InvalidPasswordException("Invalid Password"); + } + throw new InvalidCredentialException("Unknown username: " + username); + } + throw new InvalidCredentialException("Empty ObjectClassCache: " + objectClass.GetObjectClassValue()); + } + + internal virtual IEnumerable Sync(ICF.ObjectClass objectClass, int? token) + { + ObjectClassCacheEntry cache; + objectCache.TryGetValue(objectClass, out cache); + if (null != cache) + { + return cache.ObjectCache.Values.Where(x => + { + int rev = Convert.ToInt32(x.ConnectorObject.Uid.Revision); + return null == token || rev > token; + }).OrderBy(x => x.ConnectorObject.Uid.Revision).Select(x => + { + var builder = new ICF.SyncDeltaBuilder(); + builder.DeltaType = x.DeltaType; + builder.Token = new ICF.SyncToken(Convert.ToInt32(x.ConnectorObject.Uid.Revision)); + builder.Object = x.ConnectorObject; + return builder.Build(); + }); + } + return Enumerable.Empty(); + } + + private Int32 _revision = 0; + + internal virtual Int32 LatestSyncToken + { + get { return _revision; } + } + + internal virtual ICF.Uid GetNextUid(string uid) + { + return new ICF.Uid(uid, Convert.ToString(Interlocked.Increment(ref _revision))); + } + + private Int32 _id = 0; + + private ICF.Uid NewUid() + { + return GetNextUid(Convert.ToString(Interlocked.Increment(ref _id))); + } + + private readonly ConcurrentDictionary objectCache = + new ConcurrentDictionary(); + + internal virtual ObjectClassCacheEntry GeObjectCache(ICF.ObjectClass objectClass) + { + ObjectClassCacheEntry cache; + objectCache.TryGetValue(objectClass, out cache); + if (null == cache) + { + cache = new ObjectClassCacheEntry(objectClass, NewUid, GetNextUid); + ObjectClassCacheEntry rv = objectCache.GetOrAdd(objectClass, cache); + if (null != rv) + { + cache = rv; + } + } + return cache; + } + + internal class ObjectClassCacheEntry + { + private readonly ICF.ObjectClass _objectClass; + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + + private readonly ConcurrentDictionary _uniqueNameIndex = + new ConcurrentDictionary(); + + internal readonly ConcurrentDictionary ObjectCache = + new ConcurrentDictionary(); + + private readonly Func _newUid; + private readonly Func _getNextUid; + + public ObjectClassCacheEntry(ICF.ObjectClass objectClass, Func newUid, + Func getNextUid) + { + _objectClass = objectClass; + _newUid = newUid; + _getNextUid = getNextUid; + } + + internal virtual ConnectorObjectCacheEntry GetByName(string username) + { + ConnectorObjectCacheEntry entry = null; + string uid; + _uniqueNameIndex.TryGetValue(username, out uid); + if (null != uid) + { + ObjectCache.TryGetValue(uid, out entry); + } + return entry; + } + + public virtual ICF.Uid Create(ICollection createAttributes) + { + ICF.Name name = ICF.ConnectorAttributeUtil.GetNameFromAttributes(createAttributes); + if (name == null) + { + throw new InvalidAttributeValueException("__NAME__ Required"); + } + if (String.IsNullOrWhiteSpace(name.GetNameValue())) + { + throw new InvalidAttributeValueException("__NAME__ can not be blank"); + } + ICF.Uid uid = _newUid(); + + var d = _uniqueNameIndex.GetOrAdd(name.GetNameValue(), uid.GetUidValue()); + if (d == uid.GetUidValue()) + { + var builder = new ICF.ConnectorObjectBuilder { ObjectClass = _objectClass }; + builder.AddAttributes(createAttributes).SetUid(uid); + + ObjectCache.TryAdd(uid.GetUidValue(), + new ConnectorObjectCacheEntry(builder.Build(), _getNextUid)); + return uid; + } + + else + { + throw (new AlreadyExistsException()).InitUid(new ICF.Uid(name.GetNameValue())); + } + } + + public virtual ICF.Uid Update(ICF.Uid uid, ICollection updateAttributes) + { + ConnectorObjectCacheEntry entry = null; + ObjectCache.TryGetValue(uid.GetUidValue(), out entry); + if (null == entry) + { + throw new UnknownUidException(uid, _objectClass); + } + if (_lock.TryEnterWriteLock(new TimeSpan(0, 1, 0))) + { + try + { + IDictionary attributeMap = + CollectionUtil.NewCaseInsensitiveDictionary(); + foreach (ICF.ConnectorAttribute attr in entry.ConnectorObject.GetAttributes()) + { + attributeMap[attr.Name] = attr; + } + foreach (ICF.ConnectorAttribute attribute in updateAttributes) + { + if (attribute.Value == null) + { + attributeMap.Remove(attribute.Name); + } + else + { + attributeMap[attribute.Name] = attribute; + } + } + return entry.Update(attributeMap.Values); + } + finally + { + _lock.ExitWriteLock(); + } + } + else + { + throw new ConnectorException("Failed to acquire lock", + new TimeoutException("Failed to acquire lock")); + } + } + + public virtual void Delete(ICF.Uid uid) + { + ConnectorObjectCacheEntry entry = null; + ObjectCache.TryGetValue(uid.GetUidValue(), out entry); + if (null == entry) + { + throw new UnknownUidException(uid, _objectClass); + } + + + if (_lock.TryEnterWriteLock(new TimeSpan(0, 1, 0))) + { + try + { + entry.Update(entry.ConnectorObject.GetAttributes()); + entry.DeltaType = ICF.SyncDeltaType.DELETE; + } + finally + { + _lock.ExitWriteLock(); + } + } + else + { + throw new ConnectorException("Failed to acquire lock", + new TimeoutException("Failed to acquire lock")); + } + } + + public virtual IEnumerable GetIterable(Filter filter) + { + return + ObjectCache.Values.Where( + x => + !ICF.SyncDeltaType.DELETE.Equals(x.DeltaType) && + (null == filter || filter.Accept(x.ConnectorObject))).Select(x => x.ConnectorObject); + } + } + + + internal class ConnectorObjectCacheEntry + { + internal ICF.SyncDeltaType DeltaType = ICF.SyncDeltaType.CREATE; + + internal ICF.ConnectorObject ConnectorObject { get; set; } + private readonly Func _getNextUid; + + public ConnectorObjectCacheEntry(ICF.ConnectorObject connectorConnectorObject, + Func getNextUid) + { + ConnectorObject = connectorConnectorObject; + _getNextUid = getNextUid; + } + + public virtual bool Authenticate(GuardedString password) + { + ICF.ConnectorAttribute pw = ConnectorObject.GetAttributeByName(ICF.OperationalAttributes.PASSWORD_NAME); + return null != pw && null != password && ICF.ConnectorAttributeUtil.GetSingleValue(pw).Equals(password); + } + + public virtual ICF.Uid Update(ICollection updateAttributes) + { + var builder = new ICF.ConnectorObjectBuilder { ObjectClass = ConnectorObject.ObjectClass }; + builder.AddAttributes(updateAttributes).SetUid(_getNextUid(ConnectorObject.Uid.GetUidValue())); + ConnectorObject = builder.Build(); + DeltaType = ICF.SyncDeltaType.UPDATE; + return ConnectorObject.Uid; + } + } + } + + #endregion + + #region TstStatefulPoolableConnector + + [ConnectorClass("TestStatefulPoolableConnector", + "TestStatefulPoolableConnector.category", + typeof(TstStatefulConnectorConfig), + MessageCatalogPaths = new String[] { "TestBundleV1.Messages" } + )] + public class TstStatefulPoolableConnector : TstAbstractConnector, PoolableConnector + { + //public void Init(Configuration cfg) + //{ + // base.Init(cfg); + //} + + public Configuration Configuration + { + get { return _config; } + } + + public void Dispose() + { + _config = null; + } + + public void CheckAlive() + { + } + } + + #endregion +} \ No newline at end of file diff --git a/dotnet/framework/TestBundles/TestBundleV2/AssemblyInfo.cs b/dotnet/framework/TestBundles/TestBundleV2/AssemblyInfo.cs new file mode 100644 index 00000000..f35521a1 --- /dev/null +++ b/dotnet/framework/TestBundles/TestBundleV2/AssemblyInfo.cs @@ -0,0 +1,54 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +#region Using directives + +using System.Reflection; +using System.Runtime.InteropServices; + +#endregion + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TestBundleV2")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestBundleV2")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// This sets the default COM visibility of types in the assembly to invisible. +// If you need to expose a type to COM, use [ComVisible(true)] on that type. +[assembly: ComVisible(false)] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all the values or you can use the default the Revision and +// Build Numbers by using the '*' as shown below: +[assembly: AssemblyVersion("2.0.0.0")] + diff --git a/dotnet/framework/TestBundles/TestBundleV2/TestBundleV2.csproj b/dotnet/framework/TestBundles/TestBundleV2/TestBundleV2.csproj new file mode 100644 index 00000000..a7ed629f --- /dev/null +++ b/dotnet/framework/TestBundles/TestBundleV2/TestBundleV2.csproj @@ -0,0 +1,85 @@ + + + + + {3E737796-3A83-4924-9FF1-DC542F21F59E} + Debug + AnyCPU + Library + TestBundleV2 + TestBundleV2.Connector + v4.5.2 + OnBuildSuccess + + + + prompt + 4 + AnyCPU + bin\Debug\ + True + Full + False + True + DEBUG;TRACE + false + + + pdbonly + bin\Release\ + TRACE + prompt + 4 + AnyCPU + False + True + False + false + + + + + + + + + + + + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + {8B24461B-456A-4032-89A1-CD418F7B5B62} + Framework + + + + + + \ No newline at end of file diff --git a/dotnet/framework/TestBundles/TestBundleV2/TestConnector.cs b/dotnet/framework/TestBundles/TestBundleV2/TestConnector.cs new file mode 100644 index 00000000..13823a41 --- /dev/null +++ b/dotnet/framework/TestBundles/TestBundleV2/TestConnector.cs @@ -0,0 +1,63 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2012-2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; +namespace org.identityconnectors.testconnector +{ + public class TstConnectorConfig : AbstractConfiguration + { + public override void Validate() + { + } + } + + [ConnectorClass("TestConnector", + typeof(TstConnectorConfig), + MessageCatalogPaths = new String[] { "TestBundleV2.Messages" } + )] + public class TstConnector : CreateOp, Connector, SchemaOp + { + public Uid Create(ObjectClass oclass, ICollection attrs, OperationOptions options) + { + String version = "2.0"; + return new Uid(version); + } + public void Init(Configuration cfg) + { + } + + public void Dispose() + { + + } + + public Schema Schema() + { + return null; + } + } +} diff --git a/dotnet/framework/TestCommon/FrameworkInternalBridge.cs b/dotnet/framework/TestCommon/FrameworkInternalBridge.cs new file mode 100644 index 00000000..aad9f9ea --- /dev/null +++ b/dotnet/framework/TestCommon/FrameworkInternalBridge.cs @@ -0,0 +1,58 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Reflection; +using Org.IdentityConnectors.Common; + +namespace Org.IdentityConnectors.Test.Common +{ + internal static class FrameworkInternalBridge + { + private static readonly Object LOCK = new Object(); + private static Assembly _assembly = null; + /// + /// Loads a class from the FrameworkInternal module + /// + /// + /// + public static SafeType LoadType(String typeName) where T : class + { + + Assembly assembly; + lock (LOCK) + { + if (_assembly == null) + { + AssemblyName assemName = new AssemblyName(); + assemName.Name = "FrameworkInternal"; + _assembly = Assembly.Load(assemName); + } + assembly = _assembly; + } + + return SafeType.ForRawType(assembly.GetType(typeName, true)); + + } + } +} diff --git a/dotnet/framework/TestCommon/PropertyBag.cs b/dotnet/framework/TestCommon/PropertyBag.cs new file mode 100644 index 00000000..582ad3a6 --- /dev/null +++ b/dotnet/framework/TestCommon/PropertyBag.cs @@ -0,0 +1,367 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; +using Org.IdentityConnectors.Framework.Common.Objects; + +namespace Org.IdentityConnectors.Test.Common +{ + /// + /// Encapsulates a read-only bag of properties, which can be accessed in a type-safe manner. + /// + /// + /// The simplest way to obtain a required (i.e., the property must be in the bag, otherwise an exception is thrown) + /// property value is . + /// If the property is not a required one, the method can be used, + /// which also takes a default value which is returned when the property is not present in the bag. + /// + public sealed class PropertyBag + { + #region Member variables + private readonly Dictionary _bag; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the class with the properties. + /// + /// The properties contained in the bag. + internal PropertyBag(IDictionary bag) + { + _bag = new Dictionary(bag); + } + #endregion + + #region Methods + /// + /// Gets the value of a required property defined by in a type-safe manner. + /// + /// The type of the property to get. + /// The name of the property. + /// The value of the property in bag; the value might be null. + /// Thrown when no property found defined by + /// Thrown when the property exists, but its value is not of type . + /// See for details on the types that can be defined in . + public T GetProperty(string name) + { + if (!_bag.ContainsKey(name)) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, @"Property named ""{0}"" not found in bag.", name)); + } + + return CastValue(name); + } + + /// + /// Casts the value of a property defined by to the type . + /// + /// The type, the property must be cast to. + /// The name of the property. + /// The value of the property in bag cast to type ; the value might be null. + /// Thrown when the value of the property is not of type . + /// The property must be in the bag! + /// + /// The .Net BCL does not provide wrappers for primitive classes, hence to make sure that a property containing a null value + /// cannot break your code use a nullable primitive type. + /// For example: + /// + /// PropertyBag bag = TestHelpers.GetProperties(typeof(yourConnector)); + /// int? mightBeNull = bag.GetProperty("property name"); + /// + /// + /// + /// + private T CastValue(string name) + { + object value = _bag[name]; + + //if the value of the property is null then just return null, there is no need for type checking + if ((value != null) && + !(value is T)) //otherwise check if value is an instance of T + { + throw new InvalidCastException(string.Format(CultureInfo.InvariantCulture, + @"Property named ""{0}"" is of type ""{1}"" but expected type was ""{2}""", + name, value.GetType(), typeof(T))); + } + + return (T)value; + } + + /// + /// Gets a property value, returning a default value when no property with the specified name exists in the bag. + /// + /// The type of the property to get. + /// The name of the property. + /// The default value returned when no property with the specified name exists in the bag. + /// The value of the property in bag cast to type or the default value ; + /// the value might be null. + /// Thrown when the property exists, but its value is not of type . + /// See for details on the types that can be defined in . + public T GetProperty(string name, T def) + { + if (!_bag.ContainsKey(name)) + { + return def; + } + return CastValue(name); + } + + /// + /// Gets a property value, returning a default value when no property with the specified name exists in the bag. + /// + /// The name of the property. + /// The type of the property to get. + /// The default value returned when no property with the specified name exists in the bag. + /// The value of the property in bag cast to type or the default value ; + /// the value might be null. + /// Thrown when the property exists, but its value is not of type . + public dynamic GetProperty(string name, Type type, dynamic def) + { + if (!_bag.ContainsKey(name)) + { + return Convert.ChangeType(def, type); + } + object value = _bag[name]; + //if the value of the property is null then just return null, there is no need for type checking + if ((value != null) && + !(value.GetType() == type)) //otherwise check if value is an instance of T + { + if (type.IsArray) + { + var listType = typeof(List<>); + var constructedListType = listType.MakeGenericType(type.GetElementType()); + var instance = (IList)Activator.CreateInstance(constructedListType); + + var listValue = value as List; + if (listValue != null) + { + listValue.ForEach(o => instance.Add(ConvertFromString(o, type.GetElementType()))); + } + else + { + var singleSource = value as string; + if (singleSource != null) + { + instance.Add(ConvertFromString(singleSource, type.GetElementType())); + } + else + { + throw new NotSupportedException("The conversion cannot be performed."); + } + } + + object newValue = Activator.CreateInstance(type, new object[] { instance.Count }); + + instance.CopyTo((Array)newValue, 0); + return newValue; + + // return Convert.ChangeType(value, type); + // var newValue = Activator.CreateInstance(type, new object[] {1}); + // return Array.ConvertAll(new string[] {value}, null); + + } + else + { + if (value is ICollection) + { + throw new InvalidCastException(string.Format(CultureInfo.InvariantCulture, + @"Property named ""{0}"" is of type ""{1}"" but expected type was ""{2}""", + name, value.GetType(), type)); + } + else + { + // Convert simple value + return ConvertFromString((String)value, type); + } + } + } + return value; + } + + private object ConvertFromString(string sourceValue, Type target) + { + + if (typeof(string) == target) + { + return Convert.ChangeType(sourceValue, target); + } + else if (typeof(long) == target) + { + return Convert.ChangeType(sourceValue, target); + } + else if (typeof(long?) == target) + { + if (StringUtil.IsBlank(sourceValue)) + { + return null; + } + else + { + return Convert.ChangeType(sourceValue, typeof(long)); + } + } + else if (typeof(char) == target) + { + return Convert.ChangeType(sourceValue, target); + } + else if (typeof(char?) == target) + { + if (StringUtil.IsBlank(sourceValue)) + { + return null; + } + else + { + return Convert.ChangeType(sourceValue, typeof(char)); + } + } + else if (typeof(double) == target) + { + return Convert.ChangeType(sourceValue, target); + } + else if (typeof(double?) == target) + { + if (StringUtil.IsBlank(sourceValue)) + { + return null; + } + else + { + return Convert.ChangeType(sourceValue, typeof(double)); + } + } + else if (typeof(float) == target) + { + return Convert.ChangeType(sourceValue, target); + } + else if (typeof(float?) == target) + { + if (StringUtil.IsBlank(sourceValue)) + { + return null; + } + else + { + return Convert.ChangeType(sourceValue, typeof(float)); + } + } + else if (typeof(int) == target) + { + return Convert.ChangeType(sourceValue, target); + } + else if (typeof(int?) == target) + { + if (StringUtil.IsBlank(sourceValue)) + { + return null; + } + else + { + return Convert.ChangeType(sourceValue, typeof(int)); + } + } + else if (typeof(bool) == target) + { + return Convert.ChangeType(sourceValue, target); + } + else if (typeof(bool?) == target) + { + if (StringUtil.IsBlank(sourceValue)) + { + return null; + } + else + { + return Convert.ChangeType(sourceValue, typeof(bool)); + } + } + else if (typeof(Uri) == target) + { + return new Uri(sourceValue); + } + else if (typeof(FileName) == target) + { + return new FileName(sourceValue); + } + else if (typeof(GuardedByteArray) == target) + { + var result = new GuardedByteArray(); + System.Text.Encoding.UTF8.GetBytes(sourceValue).ToList().ForEach(result.AppendByte); + return result; + } + else if (typeof(GuardedString) == target) + { + var result = new GuardedString(); + sourceValue.ToCharArray().ToList().ForEach(result.AppendChar); + return result; + } + else if (typeof(Script) == target) + { + + int i = sourceValue.IndexOf('|'); + if (i > 0 && i < sourceValue.Length) + { + var scriptLanguage = sourceValue.Substring(0, i); + var scriptText = sourceValue.Substring(i + 1); + return new ScriptBuilder { ScriptLanguage = scriptLanguage, ScriptText = scriptText }.Build(); + } + else + { + throw new FormatException("Expected format is 'ScriptLanguage|ScriptText'"); + } + } + throw new NotSupportedException("The conversion cannot be performed."); + } + + /// + /// Gets a required property value known to be of type . + /// + /// The name of the property. + /// The value of the property in bag; the value might be null. + /// Thrown when no property found defined by + /// Thrown when the property exists, but its value is not of type . + /// The method expects that the value is an instance of . It does not attempt to + /// call on the value. + public string GetStringProperty(string name) + { + return GetProperty(name); + } + + /// + /// Returns a key-value pair collection that represents the current . + /// + /// A instance that represents the current . + internal IDictionary ToDictionary() + { + return CollectionUtil.NewDictionary(_bag); + } + #endregion + } +} diff --git a/dotnet/framework/TestCommon/Test.cs b/dotnet/framework/TestCommon/Test.cs new file mode 100644 index 00000000..88c43bc2 --- /dev/null +++ b/dotnet/framework/TestCommon/Test.cs @@ -0,0 +1,566 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Api.Operations; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; +using Org.IdentityConnectors.Test.Common.Spi; +using System.Globalization; +using System.Xml.Schema; + +namespace Org.IdentityConnectors.Test.Common +{ + /// + /// which stores all connector objects into + /// list retrievable with . + /// + public sealed class ToListResultsHandler + { + private IList _objects + = new List(); + + public IList Objects + { + get + { + return _objects; + } + } + + public ResultsHandler ResultsHandler + { + get + { + return new ResultsHandler + { + Handle = obj => + { + _objects.Add(obj); + return true; + } + }; + } + } + } + + /// + /// Bag of utility methods useful to connector tests. + /// + public sealed class TestHelpers + { + + private TestHelpers() + { + } + + /// + /// Method for convenient testing of local connectors. + /// + public static APIConfiguration CreateTestConfiguration(SafeType clazz, + Configuration config) + { + return GetSpi().CreateTestConfiguration(clazz, config); + } + + /// + /// Method for convenient testing of local connectors. + /// + public static APIConfiguration CreateTestConfiguration(SafeType clazz, + PropertyBag configData, string prefix) + { + return GetSpi().CreateTestConfiguration(clazz, configData, prefix); + } + + /// + /// Fills a configuration bean with data from the given map. + /// + /// + /// The map + /// keys are configuration property names and the values are + /// configuration property values. + /// + /// the configuration bean. + /// the map with configuration data. + public static void FillConfiguration(Configuration config, + IDictionary configData) + { + GetSpi().FillConfiguration(config, configData); + } + + /// + /// Creates an dummy message catalog ideal for unit testing. + /// + /// + /// All messages are formatted as follows: + /// + /// message-key: arg0.toString(), ..., argn.toString + /// + /// + /// A dummy message catalog. + public static ConnectorMessages CreateDummyMessages() + { + return GetSpi().CreateDummyMessages(); + } + + public static IList SearchToList(SearchApiOp search, + ObjectClass oclass, + Filter filter) + { + return SearchToList(search, oclass, filter, null); + } + + public static IList SearchToList(SearchApiOp search, + ObjectClass oclass, + Filter filter, + OperationOptions options) + { + ToListResultsHandler handler = new + ToListResultsHandler(); + search.Search(oclass, filter, handler.ResultsHandler, options); + return handler.Objects; + } + /// + /// Performs a raw, unfiltered search at the SPI level, + /// eliminating duplicates from the result set. + /// + /// The search SPI + /// The object class - passed through to + /// connector so it may be null if the connecor + /// allowing it to be null. (This is convenient for + /// unit tests, but will not be the case in general) + /// The filter to search on + /// The options - may be null - will + /// be cast to an empty OperationOptions + /// The list of results. + public static IList SearchToList(SearchOp search, + ObjectClass oclass, + Filter filter) where T : class + { + return SearchToList(search, oclass, filter, null); + } + /// + /// Performs a raw, unfiltered search at the SPI level, + /// eliminating duplicates from the result set. + /// + /// The search SPI + /// The object class - passed through to + /// connector so it may be null if the connecor + /// allowing it to be null. (This is convenient for + /// unit tests, but will not be the case in general) + /// The filter to search on + /// The options - may be null - will + /// be cast to an empty OperationOptions + /// The list of results. + public static IList SearchToList(SearchOp search, + ObjectClass oclass, + Filter filter, + OperationOptions options) where T : class + { + ToListResultsHandler handler = new + ToListResultsHandler(); + Search(search, oclass, filter, handler.ResultsHandler, options); + return handler.Objects; + } + + /// + /// Performs a raw, unfiltered search at the SPI level, + /// eliminating duplicates from the result set. + /// + /// The search SPI + /// The object class - passed through to + /// connector so it may be null if the connecor + /// allowing it to be null. (This is convenient for + /// unit tests, but will not be the case in general) + /// The filter to search on + /// The result handler + /// The options - may be null - will + /// be cast to an empty OperationOptions + public static void Search(SearchOp search, + ObjectClass oclass, + Filter filter, + ResultsHandler handler, + OperationOptions options) where T : class + { + GetSpi().Search(search, oclass, filter, handler, options); + } + + //At some point we might make this pluggable, but for now, hard-code + private const String IMPL_NAME = + "Org.IdentityConnectors.Framework.Impl.Test.TestHelpersImpl"; + private static readonly object LOCK = new object(); + private static TestHelpersSpi _instance; + + /// + /// Returns the instance of this factory. + /// + /// The instance of this factory + private static TestHelpersSpi GetSpi() + { + lock (LOCK) + { + if (_instance == null) + { + SafeType type = FrameworkInternalBridge.LoadType(IMPL_NAME); + _instance = type.CreateInstance(); + } + return _instance; + } + } + + private static Dictionary _propertyBags = new Dictionary(); + private const string ConfigFileName = "config.xml"; + private static readonly object ConfigurationLock = new object(); + private const string LoadConfigErrorMessage = @"TestHelpers: Unable to load optional XML configuration file ""{0}"". Exception: {1}"; + + /// + /// The name of the environment variable that contains the name of a certain configuration from which the + /// test configuration properties to be loaded besides the general properties. + /// + internal const string TestConfigEVName = "TEST_CONFIG"; + + /// + /// The name of the environment variable that contains the path of the root directory to the private configurations. + /// + internal const string PrivateConfigRootEVName = "PRIVATE_CONFIG_ROOT"; + + /// + /// The name of the environment variable, the value of which is the location of the current user's profile directory. + /// + internal const string UserProfileEVName = "USERPROFILE"; + + /// + /// Gets the configuration properties for a specified type. + /// + /// + /// + /// Gets the configuration properties for the specified from the provided . + /// + /// The type, the fully qualified name (FQN) of which to be used to identify the configuration. + /// The assembly, that contains the public configuration properties. Recommended to be the test project. + /// Bag of properties for the specified and optionally set configuration. + /// The properties are loaded from public and private configuration files, the former as manifest resources, the + /// latter as file system entries by using the specified as root prefix. Optionally, a certain test setup + /// can be used by defining the "TEST_CONFIG" environment variable that can be used to override the general configuration properties. Both + /// public and private configurations can be overridden with a certain test setup. + /// + /// Public configuration properties are loaded as manifest resources from the specified according + /// to the following: + /// + /// + /// Load the general properties from a resource, the name of which is constructed as follows: + /// type.FullName + ".config.config.xml". To achieve this, you need to create a new folder + /// in your test project named as the FQN of the specified , then create a folder called + /// "config", at last add the configuration file called "config.xml" to this folder and set its "Build Action" + /// property to "Embedded Resource". + /// + /// + /// + /// Load the configuration specific properties from a resource, the name of which is constructed + /// as follows: + /// type.FullName + ".config."+ Environment.GetEnvironmentVariable("TEST_CONFIG") + ".config.xml". + /// To achieve this, you need to create a new folder underneath the previously created "config" folder, its name + /// is defined by the configuration name and add the "config.xml" to this particular folder with "Build Action" + /// property set to "Embedded Resource". + /// + /// + /// + /// + /// + /// The private configuration properties are loaded from the file system as follows: + /// + /// + /// Load the general properties from a file, the path of which is constructed as follows: + /// Environment.GetEnvironmentVariable("PRIVATE_CONFIG_ROOT") + "\config\" + type.FullName + "\config-private\config.xml" + /// + /// + /// + /// Load the configuration specific properties from a file, the path of which is constructed as follows: + /// Environment.GetEnvironmentVariable("PRIVATE_CONFIG_ROOT") + "\config\" + type.FullName + "\config-private\" + + /// Environment.GetEnvironmentVariable("TEST_CONFIG") + "\config.xml" + /// + /// + /// + /// NOTE that if the "PRIVATE_CONFIG_ROOT" environment variable is not defined, it will be replaced in the path with the default root + /// which points to a directory of the current user's profile container, the path of which is constructed as follows: + /// Environment.GetEnvironmentVariable("USERPROFILE") + "\.connectors\" + type.Assembly.GetName().Name + /// For example: c:\Users\Administrator\.connectors\CONNECTOR_CONTAINER_ASSEMBLY_NAME\ + /// + /// + /// Thrown when the root directory of the private configuration cannot be determined. + public static PropertyBag GetProperties(Type type, Assembly assembly) + { + lock (ConfigurationLock) + { + PropertyBag bag; + if (_propertyBags.ContainsKey(type.FullName)) + { + bag = _propertyBags[type.FullName]; + } + else + { + bag = LoadProperties(type, assembly); + _propertyBags.Add(type.FullName, bag); + } + return bag; + } + } + + /// + /// Gets the configuration properties for the specified from the calling assembly. + /// + /// The type, the fully qualified name (FQN) of which to be used to identify the configuration. + /// Bag of properties for the specified and optionally set configuration. + /// See for details of the property loading mechanism. + public static PropertyBag GetProperties(Type type) + { + return GetProperties(type, Assembly.GetCallingAssembly()); + } + + /// + /// Loads the properties from public and private configurations, optionally from a certain configuration defined by + /// "TEST_CONFIG" environment variable. + /// + /// The type, the fully qualified name (FQN) of which to be used to identify the configuration. + /// The assembly, that contains the configuration resources. + /// Bag of properties for the specified . + /// Thrown when the root directory of the private configuration cannot be determined. + static PropertyBag LoadProperties(Type type, Assembly assembly) + { + string bagName = type.FullName; + string configFilePath = string.Empty; + IDictionary properties = null; + var ret = new Dictionary(); + + //load the general public properties file + configFilePath = string.Format(CultureInfo.InvariantCulture, "{0}.config.{1}", bagName, ConfigFileName); + properties = LoadConfigurationFromResource(assembly, configFilePath); + CollectionUtil.AddOrReplaceAll(ret, properties); + + //load the configuration specific public properties file + string configurationName = Environment.GetEnvironmentVariable(TestConfigEVName); + if (!StringUtil.IsBlank(configurationName)) + { + configFilePath = string.Format(CultureInfo.InvariantCulture, "{0}.config.{1}.{2}", bagName, + configurationName, ConfigFileName); + properties = LoadConfigurationFromResource(assembly, configFilePath); + CollectionUtil.AddOrReplaceAll(ret, properties); + } + + //determine the root directory of the private properties files + string privateConfigRoot = string.Empty; + if (Environment.GetEnvironmentVariable(PrivateConfigRootEVName) != null) + { + privateConfigRoot = Environment.GetEnvironmentVariable(PrivateConfigRootEVName); + } + else + { + if (Environment.GetEnvironmentVariable(UserProfileEVName) != null) + { + privateConfigRoot = Path.Combine(Environment.GetEnvironmentVariable(UserProfileEVName), + Path.Combine(".connectors", type.Assembly.GetName().Name)); + } + else + throw new InvalidOperationException( + @"Neither the ""PRIVATE_CONFIG_ROOT"" nor the ""USERPROFILE"" environment variable is defined."); + } + + privateConfigRoot = Path.Combine(privateConfigRoot, "config"); + + //load the general private properties file + configFilePath = Path.Combine(Path.Combine(Path.Combine(privateConfigRoot, bagName), "config-private"), ConfigFileName); + properties = LoadConfigurationFromFile(configFilePath); + CollectionUtil.AddOrReplaceAll(ret, properties); + + // load the configuration specific private properties file + if (!StringUtil.IsBlank(configurationName)) + { + configFilePath = Path.Combine(Path.Combine(Path.Combine(Path.Combine(privateConfigRoot, bagName), + "config-private"), configurationName), ConfigFileName); + properties = LoadConfigurationFromFile(configFilePath); + CollectionUtil.AddOrReplaceAll(ret, properties); + } + return new PropertyBag(ret); + } + + /// + /// Loads the configuration properties from the specified . + /// + /// The config file path. + /// A property name-value pair collection representing the configuration. + private static IDictionary LoadConfigurationFromFile(string filePath) + { + IDictionary properties = null; + try + { + if (File.Exists(filePath)) + { + using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + properties = ReadConfiguration(stream); + } + } + else + { + Trace.TraceWarning(@"The configuration file on path ""{0}"" is not found.", filePath); + } + } + catch (Exception e) + { + Trace.TraceInformation(LoadConfigErrorMessage, filePath, e); + } + return properties; + } + + /// + /// Loads the configuration properties from a resource defined by . + /// + /// The assembly, that contains the resource. + /// The name of the resource. + /// A property name-value pair collection representing the configuration. + private static IDictionary LoadConfigurationFromResource(Assembly assembly, string configResName) + { + IDictionary properties = null; + try + { + //the default namespace with which the resource name starts is not known, therefore + //it must be looked up in the manifest resource list + var resourceName = (from name in assembly.GetManifestResourceNames() + where name.EndsWith(configResName, StringComparison.InvariantCultureIgnoreCase) + select name).FirstOrDefault(); + if (!StringUtil.IsBlank(resourceName)) + { + using (var stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + @"Although, the configuration file called ""{0}"" exists, it cannot be accessed.", + configResName)); + } + properties = ReadConfiguration(stream); + } + } + else + { + Trace.TraceWarning(@"The configuration resource called ""{0}"" is not found.", configResName); + } + } + catch (Exception e) + { + Trace.TraceInformation(LoadConfigErrorMessage, configResName, e); + } + return properties; + } + + /// + /// Reads the configuration properties from the provided stream. + /// + /// The stream containing the configuration properties. + /// A property name-value pair collection representing the configuration. + /// The configuration properties are stored in XML format. The stream, that is opened for reading or it can be read, + /// has to contain the configuration properties in XML that adheres to the config.xsd schema embedded to + /// the assembly of this project. + /// + /// For example: + /// + /// + /// + /// + /// ]]> + /// + /// + /// Thrown when the XSD used for validating the configuration is not found in the manifest. + /// Thrown when the contains XML that does not adhere to the schema. + internal static IDictionary ReadConfiguration(Stream configStream) + { + var properties = new Dictionary(); + //validate the XML configuration against the XSD + var schemaSet = new XmlSchemaSet(); + var schemaStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Org.IdentityConnectors.Test.Common.config.xsd"); + if (schemaStream == null) + { + throw new InvalidOperationException(@"The schema used for validating of the configuration file is not found."); + } + var schemaReader = XmlReader.Create(schemaStream); + schemaSet.Add(null, schemaReader); + + //load the reader with the data stream and ignore all white space nodes + var readerSettings = new XmlReaderSettings + { + IgnoreWhitespace = true, + ValidationType = ValidationType.Schema, + Schemas = schemaSet + }; + + using (var reader = XmlReader.Create(configStream, readerSettings)) + { + //read all the nodes and extract the ones that contain properties + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && + reader.Name.Equals("property")) + { + string name = reader.GetAttribute("name"); + string xmlValue = reader.GetAttribute("value"); + if (!StringUtil.IsBlank(name) && xmlValue != null) + { + if (properties.ContainsKey(name)) + { + var value = properties[name]; + if (value is ICollection) + { + (value as ICollection).Add(xmlValue); + } + else + { + var collectionValue = new List(); + collectionValue.Add((string)properties[name]); + collectionValue.Add(xmlValue); + properties[name] = collectionValue; + } + } + else + { + properties[name] = xmlValue; + } + + } + } + } + } + return properties; + } + } +} diff --git a/dotnet/framework/TestCommon/TestCommon.csproj b/dotnet/framework/TestCommon/TestCommon.csproj new file mode 100644 index 00000000..54052cf8 --- /dev/null +++ b/dotnet/framework/TestCommon/TestCommon.csproj @@ -0,0 +1,78 @@ + + + + Debug + AnyCPU + 2.0 + {E6A207D2-E083-41BF-B522-D9D3EC09323E} + Library + Properties + Org.IdentityConnectors.Test.Common + TestCommon + v4.5.2 + 512 + FrameworkTests + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + true + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + 4.0 + + + 4.0 + + + 4.0 + + + + + + + + + + + + + + {F140E8DA-52B4-4159-992A-9DA10EA8EEFB} + Common + + + {8B24461B-456A-4032-89A1-CD418F7B5B62} + Framework + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/framework/TestCommon/TestHelpersSpi.cs b/dotnet/framework/TestCommon/TestHelpersSpi.cs new file mode 100644 index 00000000..5bea0ccb --- /dev/null +++ b/dotnet/framework/TestCommon/TestHelpersSpi.cs @@ -0,0 +1,58 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2014 ForgeRock AS. + */ +using System.Collections.Generic; + +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Framework.Api; +using Org.IdentityConnectors.Framework.Common.Objects; +using Org.IdentityConnectors.Framework.Common.Objects.Filters; +using Org.IdentityConnectors.Framework.Spi; +using Org.IdentityConnectors.Framework.Spi.Operations; + +namespace Org.IdentityConnectors.Test.Common.Spi +{ + /// + /// Private use only, do not implement! Use the methods in + /// instead. + /// + public interface TestHelpersSpi + { + APIConfiguration CreateTestConfiguration(SafeType clazz, + Configuration config); + + APIConfiguration CreateTestConfiguration(SafeType clazz, + PropertyBag configData, string prefix); + + void FillConfiguration(Configuration config, + IDictionary configData); + + SearchResult Search(SearchOp search, + ObjectClass oclass, + Filter filter, + ResultsHandler handler, + OperationOptions options) where T : class; + + ConnectorMessages CreateDummyMessages(); + } +} diff --git a/dotnet/framework/TestCommon/config.xsd b/dotnet/framework/TestCommon/config.xsd new file mode 100644 index 00000000..7063d0c5 --- /dev/null +++ b/dotnet/framework/TestCommon/config.xsd @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + diff --git a/dotnet/framework/TestCommon/version.template b/dotnet/framework/TestCommon/version.template new file mode 100644 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/TestCommon/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/WcfServiceLibrary/ConnectorServerService.cs b/dotnet/framework/WcfServiceLibrary/ConnectorServerService.cs new file mode 100755 index 00000000..fe3a4881 --- /dev/null +++ b/dotnet/framework/WcfServiceLibrary/ConnectorServerService.cs @@ -0,0 +1,514 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Configuration; +using System.Diagnostics; +using System.IdentityModel.Claims; +using System.IdentityModel.Policy; +using System.IdentityModel.Selectors; +using System.IdentityModel.Tokens; +using System.Linq; +using System.Net.WebSockets; +using System.Security.Principal; +using System.ServiceModel; +using System.ServiceModel.Activation; +using System.ServiceModel.Channels; +using System.ServiceModel.Description; +using System.ServiceModel.Dispatcher; +using System.ServiceModel.Security; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Org.ForgeRock.OpenICF.Common.ProtoBuf; +using Org.ForgeRock.OpenICF.Common.RPC; +using Org.ForgeRock.OpenICF.Framework.Remote; +using Org.IdentityConnectors.Common; +using Org.IdentityConnectors.Common.Security; + +namespace Org.ForgeRock.OpenICF.Framework.Service.WcfServiceLibrary +{ + [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] + public class WcfWebsocket : WebSocketConnectionHolder, IWebSocketService + { + private readonly IWebSocketCallback _callback; + private ConnectionPrincipal _principal; + + //WCF uses this method when it's public + private static void Configure(ServiceConfiguration configuration) + { + Trace.TraceInformation("Blank Configuration"); + } + + public WcfWebsocket() + { + _callback = OperationContext.Current.GetCallbackChannel(); + OperationContext.Current.InstanceContext.Closed += InstanceContext_Closed; + Task.Factory.StartNew(WriteMessageAsync); + } + + private void InstanceContext_Closed(object sender, EventArgs e) + { + Trace.TraceInformation("Session closed here"); + _principal.OperationMessageListener.OnClose(this, 1000, ""); + } + + public async Task OnMessage(Message message) + { + if (message == null) + { + throw new ArgumentNullException("message"); + } + + WebSocketMessageProperty property = + (WebSocketMessageProperty)message.Properties[WebSocketMessageProperty.Name]; + + + if (!message.IsEmpty) + { + byte[] body = message.GetBody(); + if (body == null || body.Length == 0) // Connection open message + { + // _principal.OperationMessageListener.OnConnect(this); + } + else if (property.MessageType == WebSocketMessageType.Text) + { + string content = Encoding.UTF8.GetString(body); + _principal.OperationMessageListener.OnMessage(this, content); + } + else if (property.MessageType == WebSocketMessageType.Binary) + { + _principal.OperationMessageListener.OnMessage(this, body); + } + else + { + //Close Message + //TODO Debug the close reason + _principal.OperationMessageListener.OnClose(this, 1000, ""); + } + } + await Task.Yield(); + } + + public void OnOpen() + { + Trace.TraceInformation("WCFWebsocket is Opened"); + _principal = Thread.CurrentPrincipal as ConnectionPrincipal; + if (_principal == null) + { + throw new Exception("Unauthenticated"); + } + _principal.OperationMessageListener.OnConnect(this); + } + + + protected override async Task WriteMessageAsync(byte[] entry, WebSocketMessageType messageType) + { + Message message = ByteStreamMessage.CreateMessage( + new ArraySegment(entry)); + message.Properties[WebSocketMessageProperty.Name] = + new WebSocketMessageProperty { MessageType = messageType }; + await _callback.OnMessage(message); + } + + private RemoteOperationContext _context; + + protected override void Handshake(HandshakeMessage message) + { + _context = _principal.Handshake(this, message); + + if (null != _context) + { + Trace.TraceInformation("Client Connection Handshake succeeded"); + } + else + { + Trace.TraceError("Client Connection Handshake failed - Close Connection"); + TryClose(); + } + } + + protected override void TryClose() + { + OperationContext.Current.Channel.Close(); + } + + public override bool Operational + { + get + { + var communicationObject = _callback as ICommunicationObject; //IContextChannel + return communicationObject != null && communicationObject.State == CommunicationState.Opened; + } + } + + public override RemoteOperationContext RemoteConnectionContext + { + get { return _context; } + } + } + + #region ConnectorServiceHostFactory + + public class ConnectorServiceHostFactory : ServiceHostFactory + { + private const string PropKey = "connectorserver.key"; + + private readonly ClientAuthenticationValidator _authenticator; + + public ConnectorServiceHostFactory(ClientAuthenticationValidator authenticator) + { + if (authenticator == null) + { + throw new ArgumentNullException("authenticator"); + } + _authenticator = authenticator; + } + + public ConnectorServiceHostFactory() + { + NameValueCollection settings = ConfigurationManager.AppSettings; + String authenticatorType = settings.Get(typeof(ClientAuthenticationValidator).FullName); + if (String.IsNullOrEmpty(authenticatorType)) + { + throw new Exception("Missing required app configuration: " + + typeof(ClientAuthenticationValidator).FullName); + } + + var type = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic) + .SelectMany(a => a.GetTypes()) + .FirstOrDefault(t => t.FullName.Equals(authenticatorType)); + if (type != null) + { + _authenticator = (ClientAuthenticationValidator)Activator.CreateInstance(type); + //Add Principal and SharedKey + String keyHash = settings.Get(PropKey); + if (null != keyHash) + { + _authenticator.Add(new SingleTenantPrincipal(new ConnectorFramework()), keyHash); + } + } + else + { + Trace.TraceWarning("Type not found: " + authenticatorType); + } + + if (null == _authenticator) + { + throw new ArgumentNullException(authenticatorType); + } + } + + public ClientAuthenticationValidator ClientAuthenticationValidator + { + get { return _authenticator; } + } + + protected override ServiceHost CreateServiceHost(Type serviceType, + Uri[] baseAddresses) + { + return new ConnectorServiceHost(_authenticator, baseAddresses); + } + } + + #endregion + + public class ConnectorServiceHost : ServiceHost + { + public ConnectorServiceHost(ClientAuthenticationValidator validator, params Uri[] baseAddresses) + : base(typeof(WcfWebsocket), baseAddresses) + { + if (validator == null) + { + throw new ArgumentNullException("validator"); + } + + // Add a custom authentication validator + Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom; + Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = validator; + + // Add a custom authorization policy + var policies = new List { validator }; + Authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom; + Authorization.ExternalAuthorizationPolicies = policies.AsReadOnly(); + + /* + foreach (var cd in ImplementedContracts.Values) + { + cd.Behaviors.Add(new WebSocketServiceInstanceProvider()); + }*/ + } + + protected override void OnClosed() + { + base.OnClosed(); + IDisposable disposable = Credentials.UserNameAuthentication.CustomUserNamePasswordValidator as IDisposable; + if (null != disposable) + { + disposable.Dispose(); + } + } + } + + #region ClientAuthenticationValidator + + public class ClientAuthenticationValidator : UserNamePasswordValidator, IAuthorizationPolicy, IDisposable + { + private readonly ConcurrentDictionary> _tenantsDictionary = + new ConcurrentDictionary>(); + + public bool Add(ConnectionPrincipal tenant, String secret) + { + var pair = new Pair(tenant, secret); + if (_tenantsDictionary.TryAdd(tenant.Identity.Name, pair)) + { + tenant.Disposed += (sender, args) => + { + IPrincipal principal = sender as IPrincipal; + Pair ignore; + if (principal != null) _tenantsDictionary.TryRemove(principal.Identity.Name, out ignore); + }; + return true; + } + return false; + } + + public override void Validate(string userName, string password) + { + // validate arguments + if (String.IsNullOrEmpty(userName)) + throw new FaultException("Unknown null or empty userName"); + if (String.IsNullOrEmpty(password)) + throw new FaultException("Unknown null or empty password"); + + Pair principal = FindPrincipal(userName); + + if (principal == null) + { + throw new SecurityTokenException("Unknown username"); + } + + if (!Verify(principal.Second, password)) + { + throw new SecurityTokenException("Unknown password"); + } + } + + public Pair FindPrincipal(String userName) + { + Pair principal; + _tenantsDictionary.TryGetValue(userName, out principal); + return principal; + } + + public static bool Verify(String principalKey, String password) + { + GuardedString key = new GuardedString(); + password.ToCharArray().ToList().ForEach(p => key.AppendChar(p)); + try + { + return key.VerifyBase64SHA1Hash(principalKey); + } + finally + { + key.Dispose(); + } + } + + #region IAuthorizationPolicy Members + + private string _id; + + public string Id + { + get { return _id ?? (_id = Guid.NewGuid().ToString()); } + } + + public bool Evaluate(EvaluationContext evaluationContext, ref object state) + { + // get the authenticated client identity + IIdentity client = GetClientIdentity(evaluationContext); + + String evaluated = state as String; + if (null == evaluated || !client.Name.Equals(evaluated)) + { + // set the custom principal + Pair principal; + + _tenantsDictionary.TryGetValue(client.Name, out principal); + if (principal != null) + { + evaluationContext.Properties["Principal"] = principal.First; + state = principal.First.Identity.Name; + } + } + return true; + } + + private IIdentity GetClientIdentity(EvaluationContext evaluationContext) + { + object obj; + if (!evaluationContext.Properties.TryGetValue("Identities", out obj)) + throw new Exception("No Identity found"); + IList identities = obj as IList; + if (identities == null || identities.Count <= 0) + throw new Exception("No Identity found"); + return identities[0]; + } + + public ClaimSet Issuer + { + get { return ClaimSet.System; } + } + + #endregion + + public void Dispose() + { + foreach (var key in _tenantsDictionary.Keys) + { + Pair pair; + _tenantsDictionary.TryRemove(key, out pair); + if (null == pair) continue; + try + { + pair.First.Dispose(); + } + catch (Exception e) + { + TraceUtil.TraceException("Failed to dispose ConnectionPrincipal:" + key, e); + } + } + } + } + + #endregion + + #region SingleTenant + + public class SingleTenantPrincipal : ConnectionPrincipal + { + private readonly ConnectorFramework _connectorFramework; + private readonly Timer _timer; + + public SingleTenantPrincipal(ConnectorFramework connectorFramework) + : this(new OpenICFServerAdapter(connectorFramework, connectorFramework.LocalManager, false)) + { + _connectorFramework = connectorFramework; + } + + public SingleTenantPrincipal( + IMessageListener listener) + : base(listener, new ConcurrentDictionary()) + { + _timer = new Timer(state => + { + if (isRunning == 1) + { + foreach (var connectionGroup in ConnectionGroups.Values) + { + Trace.TraceInformation("Check ConnectionGroup:{0} - operational={1}", connectionGroup.RemoteSessionId + , connectionGroup.Operational); + connectionGroup.CheckIsActive(); + } + } + }, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(4)); + + } + + protected override void DoClose() + { + _timer.Dispose(); + _connectorFramework.Dispose(); + } + } + + #endregion + + public class WebSocketServiceInstanceProvider : IInstanceProvider, IContractBehavior + { + private readonly Func _authenticate; + + public WebSocketServiceInstanceProvider(Func authenticate) + { + if (authenticate == null) + { + throw new ArgumentNullException("authenticate"); + } + + _authenticate = authenticate; + } + + #region IInstanceProvider Members + + public object GetInstance(InstanceContext instanceContext, Message message) + { + return GetInstance(instanceContext); + } + + public object GetInstance(InstanceContext instanceContext) + { + return new WcfWebsocket(); + } + + public void ReleaseInstance(InstanceContext instanceContext, object instance) + { + var disposable = instance as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + } + + #endregion + + #region IContractBehavior Members + + public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, + BindingParameterCollection bindingParameters) + { + } + + public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, + ClientRuntime clientRuntime) + { + } + + public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, + DispatchRuntime dispatchRuntime) + { + dispatchRuntime.InstanceProvider = this; + } + + public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) + { + } + + #endregion + } +} \ No newline at end of file diff --git a/dotnet/framework/WcfServiceLibrary/IWebSocketService.cs b/dotnet/framework/WcfServiceLibrary/IWebSocketService.cs new file mode 100755 index 00000000..722d8d68 --- /dev/null +++ b/dotnet/framework/WcfServiceLibrary/IWebSocketService.cs @@ -0,0 +1,45 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2015 ForgeRock AS. All rights reserved. + * + * The contents of this file are subject to the terms + * of the Common Development and Distribution License + * (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at + * http://forgerock.org/license/CDDLv1.0.html + * See the License for the specific language governing + * permission and limitations under the License. + * + * When distributing Covered Code, include this CDDL + * Header Notice in each file and include the License file + * at http://forgerock.org/license/CDDLv1.0.html + * If applicable, add the following below the CDDL Header, + * with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + +using System.ServiceModel; +using System.ServiceModel.Channels; +using System.Threading.Tasks; + +namespace Org.ForgeRock.OpenICF.Framework.Service.WcfServiceLibrary +{ + [ServiceContract(Namespace = "http://openicf.forgerock.org")] + public interface IWebSocketCallback + { + [OperationContract(Action = "*", IsOneWay = true)] + Task OnMessage(Message message); + } + + [ServiceContract(CallbackContract = typeof (IWebSocketCallback), Namespace = "http://openicf.forgerock.org")] + public interface IWebSocketService : IWebSocketCallback + { + [OperationContract(Action = WebSocketTransportSettings.ConnectionOpenedAction, IsInitiating = true, + IsOneWay = true)] + void OnOpen(); + } +} \ No newline at end of file diff --git a/dotnet/framework/WcfServiceLibrary/WcfServiceLibrary.csproj b/dotnet/framework/WcfServiceLibrary/WcfServiceLibrary.csproj new file mode 100755 index 00000000..7bacd0ab --- /dev/null +++ b/dotnet/framework/WcfServiceLibrary/WcfServiceLibrary.csproj @@ -0,0 +1,120 @@ + + + + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {D1771E11-C7D3-43FD-9D87-46F1231846F1} + Library + Properties + Org.ForgeRock.OpenICF.Framework.Service.WcfServiceLibrary + WcfServiceLibrary + OpenICF Framework - WCF Service Library + {3D9AD99F-2412-4246-B90B-4EAA41C64699};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + v4.5.2 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + ..\packages\Google.ProtocolBuffers.3\lib\Google.Protobuf.dll + + + + + + + + + + + + + + + + + {f140e8da-52b4-4159-992a-9da10ea8eefb} + Common + + + {5A9E8C5B-4D41-4E3E-9680-6C195BFAD47A} + FrameworkProtoBuf + + + {B85C5A35-E3A2-4B04-9693-795E57D66DE2} + FrameworkRpc + + + {5B47BEFD-C60B-4E80-943E-A7151CEEA568} + FrameworkServer + + + {8B24461B-456A-4032-89A1-CD418F7B5B62} + Framework + + + + + + + + + + + True + + + + + + \ No newline at end of file diff --git a/dotnet/framework/WcfServiceLibrary/version.template b/dotnet/framework/WcfServiceLibrary/version.template new file mode 100755 index 00000000..c085cfe1 --- /dev/null +++ b/dotnet/framework/WcfServiceLibrary/version.template @@ -0,0 +1 @@ +1.5.0.0 \ No newline at end of file diff --git a/dotnet/framework/legal/CDDLv1.txt b/dotnet/framework/legal/CDDLv1.txt new file mode 100644 index 00000000..1717cf28 --- /dev/null +++ b/dotnet/framework/legal/CDDLv1.txt @@ -0,0 +1,383 @@ + + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 + +1. Definitions. + +1.1. "Contributor" means each individual or entity that +creates or contributes to the creation of Modifications. + +1.2. "Contributor Version" means the combination of the +Original Software, prior Modifications used by a +Contributor (if any), and the Modifications made by that +particular Contributor. + +1.3. "Covered Software" means (a) the Original Software, or +(b) Modifications, or (c) the combination of files +containing Original Software with files containing +Modifications, in each case including portions thereof. + +1.4. "Executable" means the Covered Software in any form +other than Source Code. + +1.5. "Initial Developer" means the individual or entity +that first makes Original Software available under this +License. + +1.6. "Larger Work" means a work which combines Covered +Software or portions thereof with code not governed by the +terms of this License. + +1.7. "License" means this document. + +1.8. "Licensable" means having the right to grant, to the +maximum extent possible, whether at the time of the initial +grant or subsequently acquired, any and all of the rights +conveyed herein. + +1.9. "Modifications" means the Source Code and Executable +form of any of the following: + +A. Any file that results from an addition to, +deletion from or modification of the contents of a +file containing Original Software or previous +Modifications; + +B. Any new file that contains any part of the +Original Software or previous Modification; or + +C. Any new file that is contributed or otherwise made +available under the terms of this License. + +1.10. "Original Software" means the Source Code and +Executable form of computer software code that is +originally released under this License. + +1.11. "Patent Claims" means any patent claim(s), now owned +or hereafter acquired, including without limitation, +method, process, and apparatus claims, in any patent +Licensable by grantor. + +1.12. "Source Code" means (a) the common form of computer +software code in which modifications are made and (b) +associated documentation included in or with such code. + +1.13. "You" (or "Your") means an individual or a legal +entity exercising rights under, and complying with all of +the terms of, this License. For legal entities, "You" +includes any entity which controls, is controlled by, or is +under common control with You. For purposes of this +definition, "control" means (a) the power, direct or +indirect, to cause the direction or management of such +entity, whether by contract or otherwise, or (b) ownership +of more than fifty percent (50%) of the outstanding shares +or beneficial ownership of such entity. + +2. License Grants. + +2.1. The Initial Developer Grant. + +Conditioned upon Your compliance with Section 3.1 below and +subject to third party intellectual property claims, the +Initial Developer hereby grants You a world-wide, +royalty-free, non-exclusive license: + +(a) under intellectual property rights (other than +patent or trademark) Licensable by Initial Developer, +to use, reproduce, modify, display, perform, +sublicense and distribute the Original Software (or +portions thereof), with or without Modifications, +and/or as part of a Larger Work; and + +(b) under Patent Claims infringed by the making, +using or selling of Original Software, to make, have +made, use, practice, sell, and offer for sale, and/or +otherwise dispose of the Original Software (or +portions thereof). + +(c) The licenses granted in Sections 2.1(a) and (b) +are effective on the date Initial Developer first +distributes or otherwise makes the Original Software +available to a third party under the terms of this +License. + +(d) Notwithstanding Section 2.1(b) above, no patent +license is granted: (1) for code that You delete from +the Original Software, or (2) for infringements +caused by: (i) the modification of the Original +Software, or (ii) the combination of the Original +Software with other software or devices. + +2.2. Contributor Grant. + +Conditioned upon Your compliance with Section 3.1 below and +subject to third party intellectual property claims, each +Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than +patent or trademark) Licensable by Contributor to +use, reproduce, modify, display, perform, sublicense +and distribute the Modifications created by such +Contributor (or portions thereof), either on an +unmodified basis, with other Modifications, as +Covered Software and/or as part of a Larger Work; and + +(b) under Patent Claims infringed by the making, +using, or selling of Modifications made by that +Contributor either alone and/or in combination with +its Contributor Version (or portions of such +combination), to make, use, sell, offer for sale, +have made, and/or otherwise dispose of: (1) +Modifications made by that Contributor (or portions +thereof); and (2) the combination of Modifications +made by that Contributor with its Contributor Version +(or portions of such combination). + +(c) The licenses granted in Sections 2.2(a) and +2.2(b) are effective on the date Contributor first +distributes or otherwise makes the Modifications +available to a third party. + +(d) Notwithstanding Section 2.2(b) above, no patent +license is granted: (1) for any code that Contributor +has deleted from the Contributor Version; (2) for +infringements caused by: (i) third party +modifications of Contributor Version, or (ii) the +combination of Modifications made by that Contributor +with other software (except as part of the +Contributor Version) or other devices; or (3) under +Patent Claims infringed by Covered Software in the +absence of Modifications made by that Contributor. + +3. Distribution Obligations. + +3.1. Availability of Source Code. + +Any Covered Software that You distribute or otherwise make +available in Executable form must also be made available in +Source Code form and that Source Code form must be +distributed only under the terms of this License. You must +include a copy of this License with every copy of the +Source Code form of the Covered Software You distribute or +otherwise make available. You must inform recipients of any +such Covered Software in Executable form as to how they can +obtain such Covered Software in Source Code form in a +reasonable manner on or through a medium customarily used +for software exchange. + +3.2. Modifications. + +The Modifications that You create or to which You +contribute are governed by the terms of this License. You +represent that You believe Your Modifications are Your +original creation(s) and/or You have sufficient rights to +grant the rights conveyed by this License. + +3.3. Required Notices. + +You must include a notice in each of Your Modifications +that identifies You as the Contributor of the Modification. +You may not remove or alter any copyright, patent or +trademark notices contained within the Covered Software, or +any notices of licensing or any descriptive text giving +attribution to any Contributor or the Initial Developer. + +3.4. Application of Additional Terms. + +You may not offer or impose any terms on any Covered +Software in Source Code form that alters or restricts the +applicable version of this License or the recipients' +rights hereunder. You may choose to offer, and to charge a +fee for, warranty, support, indemnity or liability +obligations to one or more recipients of Covered Software. +However, you may do so only on Your own behalf, and not on +behalf of the Initial Developer or any Contributor. You +must make it absolutely clear that any such warranty, +support, indemnity or liability obligation is offered by +You alone, and You hereby agree to indemnify the Initial +Developer and every Contributor for any liability incurred +by the Initial Developer or such Contributor as a result of +warranty, support, indemnity or liability terms You offer. + +3.5. Distribution of Executable Versions. + +You may distribute the Executable form of the Covered +Software under the terms of this License or under the terms +of a license of Your choice, which may contain terms +different from this License, provided that You are in +compliance with the terms of this License and that the +license for the Executable form does not attempt to limit +or alter the recipient's rights in the Source Code form +from the rights set forth in this License. If You +distribute the Covered Software in Executable form under a +different license, You must make it absolutely clear that +any terms which differ from this License are offered by You +alone, not by the Initial Developer or Contributor. You +hereby agree to indemnify the Initial Developer and every +Contributor for any liability incurred by the Initial +Developer or such Contributor as a result of any such terms +You offer. + +3.6. Larger Works. + +You may create a Larger Work by combining Covered Software +with other code not governed by the terms of this License +and distribute the Larger Work as a single product. In such +a case, You must make sure the requirements of this License +are fulfilled for the Covered Software. + +4. Versions of the License. + +4.1. New Versions. + +Sun Microsystems, Inc. is the initial license steward and +may publish revised and/or new versions of this License +from time to time. Each version will be given a +distinguishing version number. Except as provided in +Section 4.3, no one other than the license steward has the +right to modify this License. + +4.2. Effect of New Versions. + +You may always continue to use, distribute or otherwise +make the Covered Software available under the terms of the +version of the License under which You originally received +the Covered Software. If the Initial Developer includes a +notice in the Original Software prohibiting it from being +distributed or otherwise made available under any +subsequent version of the License, You must distribute and +make the Covered Software available under the terms of the +version of the License under which You originally received +the Covered Software. Otherwise, You may also choose to +use, distribute or otherwise make the Covered Software +available under the terms of any subsequent version of the +License published by the license steward. + +4.3. Modified Versions. + +When You are an Initial Developer and You want to create a +new license for Your Original Software, You may create and +use a modified version of this License if You: (a) rename +the license and remove any references to the name of the +license steward (except to note that the license differs +from this License); and (b) otherwise make it clear that +the license contains terms which differ from this License. + +5. DISCLAIMER OF WARRANTY. + +COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" +BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED +SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR +PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY +COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE +INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF +ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF +WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF +ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS +DISCLAIMER. + +6. TERMINATION. + +6.1. This License and the rights granted hereunder will +terminate automatically if You fail to comply with terms +herein and fail to cure such breach within 30 days of +becoming aware of the breach. Provisions which, by their +nature, must remain in effect beyond the termination of +this License shall survive. + +6.2. If You assert a patent infringement claim (excluding +declaratory judgment actions) against Initial Developer or +a Contributor (the Initial Developer or Contributor against +whom You assert such claim is referred to as "Participant") +alleging that the Participant Software (meaning the +Contributor Version where the Participant is a Contributor +or the Original Software where the Participant is the +Initial Developer) directly or indirectly infringes any +patent, then any and all rights granted directly or +indirectly to You by such Participant, the Initial +Developer (if the Initial Developer is not the Participant) +and all Contributors under Sections 2.1 and/or 2.2 of this +License shall, upon 60 days notice from Participant +terminate prospectively and automatically at the expiration +of such 60 day notice period, unless if within such 60 day +period You withdraw Your claim with respect to the +Participant Software against such Participant either +unilaterally or pursuant to a written agreement with +Participant. + +6.3. In the event of termination under Sections 6.1 or 6.2 +above, all end user licenses that have been validly granted +by You or any distributor hereunder prior to termination +(excluding licenses granted to You by any distributor) +shall survive termination. + +7. LIMITATION OF LIABILITY. + +UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT +(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE +INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF +COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE +LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR +CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT +LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK +STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER +COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN +INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF +LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL +INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT +APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO +NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR +CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT +APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + +The Covered Software is a "commercial item," as that term is +defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial +computer software" (as that term is defined at 48 C.F.R. $ +252.227-7014(a)(1)) and "commercial computer software +documentation" as such terms are used in 48 C.F.R. 12.212 (Sept. +1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 +through 227.7202-4 (June 1995), all U.S. Government End Users +acquire Covered Software with only those rights set forth herein. +This U.S. Government Rights clause is in lieu of, and supersedes, +any other FAR, DFAR, or other clause or provision that addresses +Government rights in computer software under this License. + +9. MISCELLANEOUS. + +This License represents the complete agreement concerning subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the +extent necessary to make it enforceable. This License shall be +governed by the law of the jurisdiction specified in a notice +contained within the Original Software (except to the extent +applicable law, if any, provides otherwise), excluding such +jurisdiction's conflict-of-law provisions. Any litigation +relating to this License shall be subject to the jurisdiction of +the courts located in the jurisdiction and venue specified in a +notice contained within the Original Software, with the losing +party responsible for costs, including, without limitation, court +costs and reasonable attorneys' fees and expenses. The +application of the United Nations Convention on Contracts for the +International Sale of Goods is expressly excluded. Any law or +regulation which provides that the language of a contract shall +be construed against the drafter shall not apply to this License. +You agree that You alone are responsible for compliance with the +United States export administration regulations (and the export +control laws and regulation of any other countries) when You use, +distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + +As between Initial Developer and the Contributors, each party is +responsible for claims and damages arising, directly or +indirectly, out of its utilization of rights under this License +and You agree to work with Initial Developer and Contributors to +distribute such responsibility on an equitable basis. Nothing +herein is intended or shall be deemed to constitute any admission +of liability. + diff --git a/dotnet/framework/legal/ForgeRock_License.txt b/dotnet/framework/legal/ForgeRock_License.txt new file mode 100644 index 00000000..748cd377 --- /dev/null +++ b/dotnet/framework/legal/ForgeRock_License.txt @@ -0,0 +1,144 @@ +READ THIS SOFTWARE LICENSE AGREEMENT CAREFULLY. BY DOWNLOADING OR INSTALLING +THE FORGEROCK SOFTWARE, YOU, ON BEHALF OF YOURSELF AND YOUR COMPANY, AGREE TO +BE BOUND BY THIS SOFTWARE LICENSE AGREEMENT. IF YOU DO NOT AGREE TO THESE +TERMS, DO NOT DOWNLOAD OR INSTALL THE FORGEROCK SOFTWARE. + +1. Software License. + +1.1. Development Right to Use. If Company intends to or does use the ForgeRock +Software only for the purpose(s) of developing, testing, prototyping and +demonstrating its application software, then ForgeRock hereby grants Company a +nonexclusive, nontransferable, limited license to use the ForgeRock Software +only for those purposes, solely at Company's facilities and only in a +non-production environment. ForgeRock may audit Company's use of the ForgeRock +Software to confirm that a production license is not required upon reasonable +written notice to Company. If Company intends to use the ForgeRock Software in +a live environment, Company must purchase a production license and may only use +the ForgeRock Software licensed thereunder in accordance with the terms and +conditions of that subscription agreement. + +1.2. Restrictions. Except as expressly set forth in this ForgeRock Software +License Agreement (the "Agreement"), Company shall not, directly or indirectly: +(a) sublicense, resell, rent, lease, distribute or otherwise transfer rights or +usage in the ForgeRock Software, including without limitation to Company +subsidiaries and affiliates; (b) remove or alter any copyright, trademark or +proprietary notices in the ForgeRock Software; or (c) use the ForgeRock +Software in any way that would subject the ForgeRock Software, in whole in or +in part, to a Copyleft License. As used herein, "Copyleft License" means a +software license that requires that information necessary for reproducing and +modifying such software must be made available publicly to recipients of +executable versions of such software (see, e.g., GNU General Public License and +http://www.gnu.org/copyleft/). + +2. Proprietary Rights. + +2.1. ForgeRock Intellectual Property. Title to and ownership of all copies of +the ForgeRock Software whether in machine-readable (source, object code or +other format) or printed form, and all related technical know-how and all +rights therein (including without limitation all intellectual property rights +applicable thereto), belong to ForgeRock and its licensors and shall remain the +exclusive property thereof. ForgeRock's name, logo, trade names and trademarks +are owned exclusively by ForgeRock and no right is granted to Company to use +any of the foregoing except as expressly permitted herein. All rights not +expressly granted to Company are reserved by ForgeRock and its licensors. + +2.2. Suggestions. Company hereby grants to ForgeRock a royalty-free, worldwide, +transferable, sublicensable and irrevocable right and license to use, copy, +modify and distribute, including by incorporating into any product or service +owned by ForgeRock, any suggestions, enhancements, recommendations or other +feedback provided by Company relating to any product or service owned or +offered by ForgeRock. + +2.3. Source Code. The source code underlying the ForgeRock Software is +available at www.forgerock.org. + +3. Term and Termination. The terms of this Agreement shall commence on the +Effective Date and shall continue in force unless earlier terminated in +accordance this Section. This Agreement shall terminate without notice to +Company in the event Company is in material breach of any of the terms and +conditions of this Agreement. As used herein, "Effective Date" means the date +on which Company first accepted this Agreement and downloads the ForgeRock +Software. + +4. Disclaimer of Warranties. THE FORGEROCK SOFTWARE LICENSED HEREUNDER IS +LICENSED "AS IS" AND WITHOUT WARRANTY OF ANY KIND. FORGEROCK AND IT'S LICENSORS +EXPRESSLY DISCLAIM ALL WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, +INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND ANY WARRANTY OF NON-INFRINGEMENT. + +5. General Indemnification. Company shall defend, indemnify and hold ForgeRock +harmless from and against any and all liabilities, damages, losses, costs and +expenses (including but not limited to reasonable fees of attorneys and other +professionals) payable to third parties based upon any claim arising out of or +related to the use of Company's products, provided that ForgeRock: (a) promptly +notifies Company of the claim; (b) provides Company with all reasonable +information and assistance, at Company's expense, to defend or settle such a +claim; and (c) grants Company authority and control of the defense or +settlement of such claim. Company shall not settle any such claim, without +ForgeRock's prior written consent, if such settlement would in any manner +effect ForgeRock's rights in the ForgeRock Software or otherwise. ForgeRock +reserves the right to retain counsel, at ForgeRock's expense, to participate in +the defense and settlement of any such claim. + +6. Limitation of Liability. IN NO EVENT SHALL FORGEROCK BE LIABLE FOR THE COST +OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, ANY LOST PROFITS, REVENUE, OR +DATA, INTERRUPTION OF BUSINESS OR FOR ANY INCIDENTAL, SPECIAL, CONSEQUENTIAL OR +INDIRECT DAMAGES OF ANY KIND, AND WHETHER ARISING OUT OF BREACH OF WARRANTY, +BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE OR IF SUCH DAMAGE COULD HAVE +BEEN REASONABLY FORESEEN. IN NO EVENT SHALL FORGEROCK'S LIABILITY ARISING OUT +OF OR RELATED TO THIS AGREEMENT WHETHER IN CONTRACT, TORT OR UNDER ANY OTHER +THEORY OF LIABILITY, EXCEED IN THE AGGREGATE $1,000 USD. + +7. General. + +7.1. Governing Law. This Agreement shall be governed by and interpreted in +accordance with the laws of the State of California without reference to its +conflicts of law provisions. + +7.2. Assignment. Company may not assign any of its rights or obligations under +this Agreement without the prior written consent of ForgeRock, which consent +shall not be unreasonably withheld. Any assignment not in conformity with this +Section shall be null and void. + +7.3. Waiver. A waiver on one occasion shall not be construed as a waiver of any +right on any future occasion. No delay or omission by a party in exercising any +of its rights hereunder shall operate as a waiver of such rights. + +7.4. Compliance with Law. The ForgeRock Software is subject to U.S. export +control laws, including the U.S. Export Administration Act and its associated +regulations, and may be subject to export or import regulations in other +countries. Company agrees to comply with all laws and regulations of the United +States and other countries ("Export Laws") to assure that neither the ForgeRock +Software, nor any direct products thereof are; (a) exported, directly or +indirectly, in violation of Export Laws, either to any countries that are +subject to U.S. export restrictions or to any end user who has been prohibited +from participating in the U.S. export transactions by any federal agency of the +U.S. government or (b) intended to be used for any purpose prohibited by Export +Laws, including, without limitation, nuclear, chemical, or biological weapons +proliferation. + +7.5. US Government Restrictions. Company acknowledges that the ForgeRock +Software consists of "commercial computer software" and "commercial computer +software documentation" as such terms are defined in the Code of Federal +Regulations. No Government procurement regulations or contract clauses or +provisions shall be deemed a part of any transaction between the parties unless +its inclusion is required by law, or mutually agreed in writing by the parties +in connection with a specific transaction. Use, duplication, reproduction, +release, modification, disclosure or transfer of the ForgeRock Software is +restricted in accordance with the terms of this Agreement. + +7.6. Provision Severability. In the event that it is determined by a court of +competent jurisdiction that any provision of this Agreement is invalid, +illegal, or otherwise unenforceable, such provision shall be enforced as nearly +as possible in accordance with the stated intention of the parties, while the +remainder of this Agreement shall remain in full force and effect and bind the +parties according to its terms. To the extent any provision cannot be enforced +in accordance with the stated intentions of the parties, such terms and +conditions shall be deemed not to be a part of this Agreement. + +7.7. Entire Agreement. This Agreement constitutes the entire and exclusive +agreement between the parties with respect to the subject matter hereof and +supersede any prior agreements between the parties with respect to such subject +matter + diff --git a/dotnet/framework/packages/Google.ProtocolBuffers.3/lib/Google.Protobuf.dll b/dotnet/framework/packages/Google.ProtocolBuffers.3/lib/Google.Protobuf.dll new file mode 100755 index 00000000..0eb3ec70 Binary files /dev/null and b/dotnet/framework/packages/Google.ProtocolBuffers.3/lib/Google.Protobuf.dll differ diff --git a/dotnet/framework/packages/Google.ProtocolBuffers.3/lib/Google.Protobuf.pdb b/dotnet/framework/packages/Google.ProtocolBuffers.3/lib/Google.Protobuf.pdb new file mode 100755 index 00000000..7020c62e Binary files /dev/null and b/dotnet/framework/packages/Google.ProtocolBuffers.3/lib/Google.Protobuf.pdb differ diff --git a/dotnet/framework/packages/Google.ProtocolBuffers.3/tools/protoc.exe b/dotnet/framework/packages/Google.ProtocolBuffers.3/tools/protoc.exe new file mode 100755 index 00000000..ce4fa3c8 Binary files /dev/null and b/dotnet/framework/packages/Google.ProtocolBuffers.3/tools/protoc.exe differ diff --git a/dotnet/framework/server.pfx b/dotnet/framework/server.pfx new file mode 100644 index 00000000..63f632a2 Binary files /dev/null and b/dotnet/framework/server.pfx differ