/*
* ====================
* 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://IdentityConnectors.dev.java.net/legal/license.txt
* 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 identityconnectors/legal/license.txt.
* 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 Org.IdentityConnectors.Common;
using Org.IdentityConnectors.Common.Pooling;
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;
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;
///
/// 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 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.GetPool();
// 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 =
(APIOperationRunner)_runnerImplConstructor.Invoke(new object[]{
_context,
connector});
ret = method.Invoke(runner, args);
// call out to the operation..
}
catch (TargetInvocationException e)
{
Exception root = e.InnerException;
ExceptionUtil.PreserveStackTrace(root);
throw root;
}
finally
{
// make sure dispose of the connector properly
if (connector != null)
{
// 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 propogate 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);
}
}
}
}
return ret;
}
}
#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
{
private readonly ObjectPool _pool;
public ConnectorOperationalContext(LocalConnectorInfoImpl connectorInfo,
APIConfigurationImpl apiConfiguration,
ObjectPool pool)
: base(connectorInfo, apiConfiguration)
{
_pool = pool;
}
public ObjectPool GetPool()
{
return _pool;
}
public SafeType GetConnectorClass()
{
return GetConnectorInfo().ConnectorClass;
}
}
#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");
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");
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 oclass, ICollection attributes, OperationOptions options)
{
Assertions.NullCheck(oclass, "oclass");
Assertions.NullCheck(attributes, "attributes");
//convert null into empty
if (options == null)
{
options = new OperationOptionsBuilder().Build();
}
HashSet dups = new HashSet();
foreach (ConnectorAttribute attr in attributes)
{
if (dups.Contains(attr.Name))
{
throw new ArgumentException("Duplicate attribute name exists: " + attr.Name);
}
dups.Add(attr.Name);
}
if (oclass == null)
{
throw new ArgumentException("Required attribute ObjectClass not found!");
}
Connector connector = GetConnector();
ObjectNormalizerFacade normalizer = GetNormalizer(oclass);
ICollection normalizedAttributes =
normalizer.NormalizeAttributes(attributes);
// create the object..
Uid ret = ((CreateOp)connector).Create(oclass, attributes, 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 objClass, Uid uid, OperationOptions options)
{
Assertions.NullCheck(objClass, "objClass");
Assertions.NullCheck(uid, "uid");
//convert null into empty
if (options == null)
{
options = new OperationOptionsBuilder().Build();
}
Connector connector = GetConnector();
ObjectNormalizerFacade normalizer = GetNormalizer(objClass);
// delete the object..
((DeleteOp)connector).Delete(objClass,
(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 bool Handle(ConnectorObject obj)
{
// clone the object and reduce the attributes only the set of
// attributes.
return _handler(ReduceToAttrsToGet(obj));
}
}
#endregion
#region SearchAttributesToGetResultsHandler
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 bool Handle(SyncDelta 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(bld.Build());
}
}
#endregion
#region DuplicateFilteringResultsHandler
public sealed class DuplicateFilteringResultsHandler
{
// =======================================================================
// Fields
// =======================================================================
private readonly ResultsHandler _handler;
private readonly HashSet _visitedUIDs = new HashSet();
private bool _stillHandling;
// =======================================================================
// Constructors
// =======================================================================
///
/// Filter chain for producers.
///
/// Producer to filter.
public DuplicateFilteringResultsHandler(ResultsHandler handler)
{
// there must be a producer..
if (handler == null)
{
throw new ArgumentException("Handler must not be null!");
}
this._handler = handler;
}
public bool Handle(ConnectorObject obj)
{
String uid =
obj.Uid.GetUidValue();
if (!_visitedUIDs.Add(uid))
{
//we've already seen this - don't pass it
//throw
return true;
}
_stillHandling = _handler(obj);
return _stillHandling;
}
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 PassThruFilter() : filter;
}
public bool Handle(ConnectorObject obj)
{
if (filter.Accept(obj))
{
return handler(obj);
}
else
{
return true;
}
}
///
/// Use a pass thru filter to use if a null filter is provided.
///
class PassThruFilter : Filter
{
public bool Accept(ConnectorObject obj)
{
return true;
}
}
}
#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 bool Handle(ConnectorObject 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 objClass, Uid uid, OperationOptions options)
{
Assertions.NullCheck(objClass, "objClass");
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(objClass, filter, new ResultsHandler(adapter.Handle), 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 connection.
///
public class OperationalContext
{
///
/// ConnectorInfo
///
private readonly LocalConnectorInfoImpl connectorInfo;
///
/// Contains the .
///
private readonly APIConfigurationImpl apiConfiguration;
public OperationalContext(LocalConnectorInfoImpl connectorInfo,
APIConfigurationImpl apiConfiguration)
{
this.connectorInfo = connectorInfo;
this.apiConfiguration = apiConfiguration;
}
public Configuration GetConfiguration()
{
return CSharpClassProperties.CreateBean((ConfigurationPropertiesImpl)this.apiConfiguration.ConfigurationProperties,
connectorInfo.ConnectorConfigurationClass);
}
protected LocalConnectorInfoImpl GetConnectorInfo()
{
return connectorInfo;
}
public ResultsHandlerConfiguration getResultsHandlerConfiguration()
{
return apiConfiguration.ResultsHandlerConfiguration;
}
}
#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 bool Handle(ConnectorObject obj)
{
ConnectorObject normalized = _normalizer.NormalizeObject(obj);
return _target(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 bool Handle(SyncDelta delta)
{
SyncDelta normalized = _normalizer.NormalizeSyncDelta(delta);
return _target(normalized);
}
}
#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();
BuildReferenceList2(assembly, list, new HashSet());
return list.ToArray();
}
private void BuildReferenceList2(Assembly assembly,
List list,
HashSet visited)
{
bool notThere = visited.Add(assembly.GetName().FullName);
if (notThere)
{
list.Add(assembly);
foreach (AssemblyName referenced in assembly.GetReferencedAssemblies())
{
Assembly assembly2 = Assembly.Load(referenced);
BuildReferenceList2(assembly2, list, visited);
}
}
}
}
#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 void Search(ObjectClass oclass, Filter originalFilter, ResultsHandler handler, OperationOptions options)
{
Assertions.NullCheck(oclass, "oclass");
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 finalFilter = originalFilter;
if (hdlCfg.EnableNormalizingResultsHandler) {
ObjectNormalizerFacade normalizer = GetNormalizer(oclass);
//chain a normalizing handler (must come before
//filter handler)
ResultsHandler normalizingHandler = new NormalizingResultsHandler(handler, normalizer).Handle;
Filter normalizedFilter = normalizer.NormalizeFilter(originalFilter);
// chain a filter handler..
if (hdlCfg.EnableFilteredResultsHandler) {
// chain a filter handler..
handlerChain = new FilteredResultsHandler(handler, normalizedFilter).Handle;
finalFilter = normalizedFilter;
} else {
handlerChain = normalizingHandler;
}
} else if (hdlCfg.EnableFilteredResultsHandler) {
// chain a filter handler..
ResultsHandler filteredHandler = new FilteredResultsHandler(handlerChain, originalFilter).Handle;
}
//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(
handler, attrsToGet).Handle;
}
searcher.RawSearch(GetConnector(), oclass, finalFilter, handlerChain, options);
}
}
#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,
ResultsHandler handler,
OperationOptions options);
}
#endregion
#region RawSearcherImpl
internal class RawSearcherImpl : RawSearcher where T : class
{
public void RawSearch(Object search,
ObjectClass oclass,
Filter filter,
ResultsHandler 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,
ResultsHandler 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.Handle;
}
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 void Sync(ObjectClass objClass, SyncToken token,
SyncResultsHandler handler,
OperationOptions options)
{
//token is allowed to be null, objClass and handler must not be null
Assertions.NullCheck(objClass, "objClass");
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).Handle;
}
//chain a normalizing results handler
ObjectNormalizerFacade normalizer =
GetNormalizer(objClass);
handler = new NormalizingSyncResultsHandler(handler, normalizer).Handle;
((SyncOp)GetConnector()).Sync(objClass, token, handler, options);
}
public SyncToken GetLatestSyncToken(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 objclass,
Uid uid,
ICollection valuesToAdd,
OperationOptions options)
{
// validate all the parameters..
ValidateInput(objclass, uid, valuesToAdd, true);
//cast null as empty
if (options == null)
{
options = new OperationOptionsBuilder().Build();
}
ObjectNormalizerFacade normalizer =
GetNormalizer(objclass);
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(objclass, uid, valuesToAdd, options);
}
else
{
ICollection replaceAttributes =
FetchAndMerge(objclass, uid, valuesToAdd, true, options);
ret = op.Update(objclass, uid, replaceAttributes, options);
}
return (Uid)normalizer.NormalizeAttribute(ret);
}
public Uid RemoveAttributeValues(ObjectClass objclass,
Uid uid,
ICollection valuesToRemove,
OperationOptions options)
{
// validate all the parameters..
ValidateInput(objclass, uid, valuesToRemove, true);
//cast null as empty
if (options == null)
{
options = new OperationOptionsBuilder().Build();
}
ObjectNormalizerFacade normalizer =
GetNormalizer(objclass);
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(objclass, uid, valuesToRemove, options);
}
else
{
ICollection replaceAttributes =
FetchAndMerge(objclass, uid, valuesToRemove, false, options);
ret = op.Update(objclass, 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