datacommsserver/esockserver/CoreProviders/src/coretiernotificationactivity.cpp
changeset 0 dfb7c4ff071f
child 4 928ed51ddc43
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/datacommsserver/esockserver/CoreProviders/src/coretiernotificationactivity.cpp	Thu Dec 17 09:22:25 2009 +0200
@@ -0,0 +1,829 @@
+// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+//
+
+/**
+ @file
+ @internalTechnology
+*/
+
+
+#include <e32def.h>
+#include <commsdattypesv1_1.h> // CommsDat
+#include <metadatabase.h> // CommsDat
+
+#include <comms-infras/ss_activities.h>
+
+#include <comms-infras/cs_connservparams.h>
+#include <comms-infras/cs_connservparams_internal.h>
+#include <elements/nm_messages_child.h>
+
+#include "coretiernotificationactivity.h"
+#include <comms-infras/coretiernotificationstates.h>
+#include <comms-infras/coretiernotificationcollectors.h>
+#include <comms-infras/ss_nodemessages_tiermanager.h>
+
+#include <comms-infras/es_connectionservparameterbundletrace.h>
+#include <comms-infras/es_connectionservparameterbundletraceimpl.h> // include this once per dll
+
+
+#ifdef _DEBUG
+// Panic category for "absolutely impossible!" vanilla ASSERT()-type panics from this module
+// (if it could happen through user error then you should give it an explicit, documented, category + code)
+_LIT(KSpecAssert_ESockCrPrvTNotAC, "ESockCrPrvTNotAC");
+#endif
+
+
+
+using namespace CommsDat;
+using namespace ESock;
+using namespace TierNotification;
+using namespace NetStateMachine;
+using namespace ConnectionServ;
+using namespace CoreStates;
+using namespace Messages;
+using namespace MeshMachine;
+
+
+template <class T>
+class CleanupResetAndDestroy
+	{
+public:
+	inline static void PushL(T& aRef);
+private:
+	static void ResetAndDestroy(TAny *aPtr);
+	};
+
+template <class T>
+inline void CleanupResetAndDestroyPushL(T& aRef);
+
+
+template <class T>
+inline void CleanupResetAndDestroy<T>::PushL(T& aRef)
+	{
+	CleanupStack::PushL(TCleanupItem(&ResetAndDestroy,&aRef));
+	}
+
+template <class T>
+void CleanupResetAndDestroy<T>::ResetAndDestroy(TAny *aPtr)
+	{
+	static_cast<T*>(aPtr)->ResetAndDestroy();
+	}
+
+template <class T>
+inline void CleanupResetAndDestroyPushL(T& aRef)
+	{
+	CleanupResetAndDestroy<T>::PushL(aRef);
+	}
+
+
+//
+// Tier Notification
+//
+// The reference tier status query / notification activity.
+//
+namespace TMTierNotificationActivity
+{
+DEFINE_EXPORT_CUSTOM_NODEACTIVITY(ECFActivityTierNotification, TierNotify, Messages::TNodeSignal::TNullMessageId, CTierNotificationActivity::NewL)
+	FIRST_NODEACTIVITY_ENTRY(TierNotification::TAwaitingTierNotificationRegistration, TNoTag)
+	NODEACTIVITY_ENTRY(KNoTag, TierNotification::TInitTierNotifications, TierNotification::TAwaitingDataCollectors, TCancelOrErrorOrTag<KNoTag>)
+	NODEACTIVITY_ENTRY(KNoTag, TierNotification::TStartTierNotifications, TierNotification::TAwaitingCancelOrErrorOrDestroy, TCancelOrErrorOrTag<KNoTag>)
+	THROUGH_TRIPLE_ENTRY(KErrorTag, MeshMachine::TStoreError, TTag<KCancelTag>)
+	NODEACTIVITY_ENTRY(KCancelTag, TierNotification::TInitiateTierNotificationCancel, TierNotification::TAwaitingDestroy, TNoTag)
+	LAST_NODEACTIVITY_ENTRY(KNoTag, MeshMachine::TSetIdle)
+NODEACTIVITY_END()
+}
+
+namespace TMTierNotificationGetDataCollectorsActivity
+{
+DEFINE_EXPORT_NODEACTIVITY(ECFActivityTierGetDataCollectors, GetCollectors, TCFDataCollector::TTierGetDataCollectors)
+	NODEACTIVITY_ENTRY(KNoTag, TierNotification::TGetDataCollectors, MeshMachine::TAwaitingAny, MeshMachine::TNoTag)
+NODEACTIVITY_END()
+}
+
+
+EXPORT_C /*static*/ MeshMachine::CNodeActivityBase* CTierNotificationActivity::NewL(const MeshMachine::TNodeActivity& aActivitySig, MeshMachine::AMMNodeBase& aNode)
+	{
+	TUint c = GetNextActivityCountL(aActivitySig,aNode);
+	CTierNotificationActivity* inst = new (ELeave) CTierNotificationActivity(aActivitySig, aNode, c);
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "NewL()"),inst);
+	return inst;
+	}
+
+
+/*virtual*/ CTierNotificationActivity::~CTierNotificationActivity()
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "~CTierNotificationActivity()"),this);
+
+	if (iQueryBundleOwner)
+	    {
+	    iQueryBundleOwner->Close();
+	    }
+	iCollectorSessions.ResetAndDestroy();
+	delete iCache;
+	delete iDbSession;
+	}
+
+
+/*virtual*/ void CTierNotificationActivity::InitL(CRefCountOwnedParameterBundle* aQuery, const RMessage2& aPlatSecInfo, TBool aOneOffQuery)
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "StartL()"),this);
+
+	// note we don't open a reference since we take the ownership from the DoL
+	// that calls us
+	__ASSERT_DEBUG(!iQueryBundleOwner, User::Panic(KSpecAssert_ESockCrPrvTNotAC, 1));
+	iQueryBundleOwner = aQuery;
+	iOneOffQuery = aOneOffQuery;
+
+	if(!HasSufficientCapabilities(aPlatSecInfo))
+		{
+		User::Leave(KErrPermissionDenied);
+		}
+	}
+	// now pass control back to the mesh so a separate activity can decide on the required collectors
+
+
+/*virtual*/ void CTierNotificationActivity::StartL(RPointerArray<MDataCollector>& aCollectors)
+	{
+	CleanupResetAndDestroyPushL(aCollectors);
+	if(aCollectors.Count() == 0)
+		{
+		User::Leave(KErrArgument);
+		}
+
+	iDbSession = CMDBSession::NewL(KCDVersion1_2);
+
+	iCache = CTierNotificationCache::NewL();
+
+	// set up a session with each collector
+	for(TInt sessionId=0 ; sessionId<aCollectors.Count() ; ++sessionId)
+		{
+		CDataCollectorSession* newCollector = CDataCollectorSession::NewL(aCollectors[sessionId], *this, sessionId );
+		__ASSERT_DEBUG(newCollector, User::Panic(KSpecAssert_ESockCrPrvTNotAC, 2));
+		iCollectorSessions.AppendL(newCollector);
+		aCollectors[sessionId] = 0; // now owned by *this so don't want it to be in the cleanup path twice!
+		}
+
+	CleanupStack::PopAndDestroy();
+
+	// Must run session creation and start loops separately in case the initial data fetch is synchronous.
+	// Reasoning:
+	//  when a full set of data for each collector is received, notification is only allowed if all
+	//  collectors know they've got their full set.
+	//   so all collectors must already be available to answer that question by the time this loop runs.
+	for(TInt i=0; i<iCollectorSessions.Count() ; ++i)
+		{
+		iCollectorSessions[i]->StartL();
+		}
+	}
+
+
+TBool CompareTypeIds(const STypeId& aFirst, const STypeId& aSecond)
+    {
+    return( aFirst == aSecond );
+    }
+
+
+
+EXPORT_C void CTierNotificationActivity::ReportChangesInCacheL()
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "ReportChangesInCacheL()"),this);
+
+	const CConnectionServParameterBundle* queryBundle = static_cast<const CConnectionServParameterBundle*>(iQueryBundleOwner->Ptr());
+	const RArray<Meta::STypeId>& typesToReturn = queryBundle->GetParameterSetsToReturnL();
+
+	CParameterBundleBase* resultBundle = NULL;
+	CRefCountOwnedParameterBundle* bundleOwner = NULL;
+	CParameterSetContainer* cacheParSetCtr = NULL;
+	XNotificationCacheParameterSet* cacheParams = NULL;
+	TInt i = 0;
+
+	while ((cacheParSetCtr = iCache->Get(i++)) != NULL)
+		{
+		cacheParams = XNotificationCacheParameterSet::FindInParamSetContainer(*cacheParSetCtr);
+		__ASSERT_DEBUG(cacheParams, User::Panic(KSpecAssert_ESockCrPrvTNotAC, 3));
+
+		if(cacheParams->ShouldReport())
+			{
+			cacheParams->ClearToReport();
+
+			if (!iOneOffQuery && iMessageCount == 0)
+				{
+				// Swallow first notification (pseudo "status report").
+				//  This must be done because the first notification arises
+				//   as a result of the difference between UNKNOWN
+				//   and discovering the MATCHING data for the first time
+				//   meaning that this data enters the "match set".
+				//  This way only notifications of CHANGE will be sent
+				//   N.B. a client must run a tier query AFTER it's registered
+				//   for notification, in order to ensure its view of the data
+				//   is up to date.
+				continue;
+				}
+
+			// create a new bundle when we get the first result
+			// (if bundleOwner is NULL then resultBundle is also NULL)
+			if (!bundleOwner)
+				{
+				resultBundle = CParameterBundleBase::NewL();
+				CleanupStack::PushL(resultBundle);
+				bundleOwner = new(ELeave)CRefCountOwnedParameterBundle(resultBundle);
+				CleanupStack::Pop();
+
+				bundleOwner->Open();
+                CleanupClosePushL(*bundleOwner);
+				}
+
+			CParameterSetContainer* newParSetCtr = CParameterSetContainer::NewL(*resultBundle,cacheParSetCtr->Id());
+			const XParameterSetBase* cacheSet;
+			TInt j=0;
+			while((cacheSet = cacheParSetCtr->GetParameterSet(j++)) != NULL)
+				{
+				// don't copy the cache parameters
+				if(cacheSet == cacheParams) continue;
+
+				if(typesToReturn.Find(cacheSet->GetTypeId(),TIdentityRelation<STypeId>(CompareTypeIds)) == KErrNotFound )
+					{
+					_TIER_LOG_3(_L8("\t.. not including type 0x %08x , %d"),cacheSet->GetTypeId().iUid.iUid,cacheSet->GetTypeId().iType);
+					continue;
+					}
+
+				XConnectionServParameterSet* newSet = static_cast<XConnectionServParameterSet*>(XParameterSetBase::NewInstanceL(cacheSet->GetTypeId()));
+				CleanupStack::PushL(newSet);
+				newParSetCtr->AddParameterSetL(newSet);
+				CleanupStack::Pop(newSet);
+
+				// this filters out any information we didn't ask for
+				newSet->CopyFieldsRequiredByQueryFromL(*queryBundle, static_cast<const XConnectionServParameterSet&>(*cacheSet));
+				}
+
+//			if(newParSetCtr->GetParameterSet(0) == NULL)
+//				{
+//				rob reject param set ctr if no types contained therein
+//				}
+			}
+		}
+	iMessageCount++;
+
+	__ASSERT_DEBUG(iOriginators.Count(), User::Panic(KSpecAssert_ESockCrPrvTNotAC, 4));
+
+	// ok, result bundle has now been generated, let's send it to the originator.
+	//   ownership is given to the recipient so pop it from the stack first
+	if (bundleOwner)
+		{
+		CleanupStack::Pop(bundleOwner);
+		}
+
+
+	if (iOneOffQuery)
+		{
+		if (!bundleOwner)
+			{
+			// ok.. so no results were found in query...
+			//   so create an empty bundle to signify this.
+			resultBundle = CParameterBundleBase::NewL();
+			CleanupStack::PushL(resultBundle);
+			bundleOwner = new(ELeave) CRefCountOwnedParameterBundle(resultBundle);
+			CleanupStack::Pop();
+
+			bundleOwner->Open();
+			}
+
+		_TIER_LOG_BUNDLE("About to send result.", resultBundle);
+		TCFTierStatusProvider::TTierStatus msg(bundleOwner);
+		PostRefCountedBundleToOriginators(msg, *bundleOwner);
+		}
+	else // notification then
+		{
+		if(!bundleOwner)
+			{
+			if(iMessageCount == 1)
+				{
+				// ok.. so or notification has started up.
+				//   so create an empty bundle to signify this.
+				resultBundle = CParameterBundleBase::NewL();
+    			CleanupStack::PushL(resultBundle);
+    			bundleOwner = new(ELeave) CRefCountOwnedParameterBundle(resultBundle);
+    			CleanupStack::Pop();
+    			bundleOwner->Open();
+				}
+			else
+				{
+				// don't send empty bundles for notification
+				_TIER_LOG(_L8("\tSomething changed but it's not appropriate to generate a notification.."));
+				return;
+				}
+			}
+
+    	_TIER_LOG_BUNDLE("About to send notification.", resultBundle);
+		TCFTierStatusProvider::TTierNotification msg(bundleOwner);
+		PostRefCountedBundleToOriginators(msg, *bundleOwner);
+		}
+
+	// and finally release our ref on the bundle
+	bundleOwner->Close();
+	}
+
+
+void CTierNotificationActivity::PostRefCountedBundleToOriginators(const Messages::TSignatureBase& aMsg, CRefCountOwnedParameterBundle& aBundleOwner)
+    {
+    TInt originators = iOriginators.Count();
+
+    for (TInt i=0; i<originators; i++)
+        {
+        // Open as many refcounts as there are originators before we do the Post.
+        // This avoids a potential race where an originator, and the aBundleOwner's
+        // creator is running in a higher priority thread than this. If both of
+        // these conditions are met the bundle may be destroyed before we finish
+        // sending. By ensuring "enough" refs are opened before we send we can
+        // avoid this.
+        aBundleOwner.Open();
+        }
+
+    TInt unusedRefs = originators - PostToOriginators(aMsg);
+
+    // Close any unused refcounts on the bundle
+    while (unusedRefs-- > 0)
+        {
+        aBundleOwner.Close();
+        }
+    }
+
+
+EXPORT_C TBool CTierNotificationActivity::AllCollectorsUnlocked() const
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "AllCollectorsUnlocked()"),this);
+	for(TInt i=0 ; i<iCollectorSessions.Count() ; ++i)
+		{
+		if(iCollectorSessions[i]->IsLocked())
+			{
+			return EFalse;
+			}
+		}
+	return ETrue;
+	}
+
+TBool CTierNotificationActivity::AllAccessPointsReadyForAllCollectors() const
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "AllAccessPointsReadyForAllCollectors()"),this);
+
+	// possible optimisation here for notification mode
+	//  once we've reached return True we could set a flag.. which it would be up to
+	//   disoverers of new access points to clear when it sees them, so we can start running the
+	//    check again.
+
+	CParameterSetContainer* cacheParSetCtr;
+	XNotificationCacheParameterSet* cacheParams;
+	TInt i = 0;
+	while((cacheParSetCtr = iCache->Get(i++)) != NULL)
+		{
+		_TIER_LOG_PSC("Checking if ready: ",cacheParSetCtr);
+		cacheParams = XNotificationCacheParameterSet::FindInParamSetContainer(*cacheParSetCtr);
+		__ASSERT_DEBUG(cacheParams, User::Panic(KSpecAssert_ESockCrPrvTNotAC, 5));
+		if( ! cacheParams->ReadyToReport())
+			{
+			return EFalse;
+			}
+		}
+	return ETrue;
+	}
+
+
+EXPORT_C void CTierNotificationActivity::ReportIfReadyL()
+	{
+	if( ! AllCollectorsUnlocked() )
+		{
+		return;
+		}
+
+	if( ! AllAccessPointsReadyForAllCollectors() )
+		{
+		return;
+		}
+
+	// If we got this far then all sessions are unlocked. In the case of a query we should
+	//  now initiate shutdown of all collectors before formulating our response.
+	// This is because TierStatus is the last message this activity will send
+	//  so it must be possible for the client to leave this node as soon as it has received TierStatus.
+	//
+	if(iOneOffQuery)
+		{
+		InitiateShutdown(KErrNone); // will send the results once everything is cleaned up
+		}
+	else
+		{
+		// for notify mode this will just mark the cache instead of formulating and sending a response.
+		//  actual change notifications will happen via DataReceived
+		ReportChangesInCacheL();
+		}
+	return;
+	}
+
+
+EXPORT_C void CTierNotificationActivity::InitiateShutdown(TInt aErrorToReport)
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "ShutdownFinished()"),this);
+	if(iShuttingDown)
+		{
+		return;
+		}
+	iShuttingDown=ETrue;
+
+	SetError(aErrorToReport);
+	TInt i=0 ;
+	for( ; i<iCollectorSessions.Count() ; ++i)
+		{
+		CDataCollectorSession* sess = iCollectorSessions[i];
+		if(sess)
+			{
+			sess->Shutdown();
+			}
+		}
+	if(i==0)
+		{
+		ShutdownFinished();
+		}
+	}
+
+
+EXPORT_C void CTierNotificationActivity::ShutdownFinished()
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "ShutdownFinished()"),this);
+	for(TInt i=0 ; i<iCollectorSessions.Count() ; ++i)
+		{
+		CDataCollectorSession* sess = iCollectorSessions[i];
+		if(sess->HasShutDown())
+			return;
+		}
+
+	// If we got this far then all sessions are shut down. In the case of a query we should
+	//  now fomulate our response and send it.
+	// Otherwise error originators saying we cancelled.
+	//
+	if(iOneOffQuery)
+		{
+		TInt err = Error();
+		if(err == KErrNone)
+			{
+			TRAP(err,ReportChangesInCacheL());
+			}
+		if(err != KErrNone)
+			{
+			TEBase::TError msg(TCFTierStatusProvider::TTierStatusQuery::Id(), Error());
+			PostToOriginators(msg);
+			}
+		}
+	else
+		{
+		__ASSERT_DEBUG(Error() != KErrNone, User::Panic(KSpecAssert_ESockCrPrvTNotAC, 6));
+		TEBase::TError msg(TCFTierStatusProvider::TTierNotificationRegistration::Id(), Error());
+		PostToOriginators(msg);
+		}
+
+	// Any error has been handled. This prevents panic of mesh machinery
+	SetError(KErrNone);
+
+	// Our query/notification activity has finished.
+	//  We need to send a message back to ourselves so the node can finish
+	//   processing the Activity as defined in the map. We are (ab)using the
+	//    TDestroy message for this purpose :-I
+	TNodeCtxId us(ActivityId(),iNode.Id());
+	RClientInterface::OpenPostMessageClose(us, us, TEChild::TDestroy().CRef());
+	return;
+	}
+
+
+
+/*virtual*/ TBool CTierNotificationActivity::HasSufficientCapabilities(const RMessage2& aPlatSecInfo) const
+	{
+	// default.. all necessary policing has been done back at RConnectionServ IPC level.
+	//  Specific technology implementations may wish to override this function in order to examine the client capabilities more deeply.
+	(void)aPlatSecInfo;
+	return ETrue;
+	}
+
+/*virtual*/ MEqualityComparator* CTierNotificationActivity::CreateEqualityComparatorL(const CParameterSetContainer& aData)
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "CreateEqualityComparatorL()"),this);
+	return new(ELeave) CAccessPointIdComparator(aData);
+	}
+
+
+EXPORT_C void CTierNotificationActivity::DataReceivedL(CDataCollectorSession& aSource, CParameterSetContainer& aNewData, TBool aAllowNotify)
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "DataReceivedL()"),this);
+	_TIER_LOG_PSC("Data received: ",&aNewData);
+
+	MEqualityComparator* comparator = CreateEqualityComparatorL(aNewData);
+	CleanupStack::PushL(comparator);
+
+	CParameterSetContainer* cacheEntry = iCache->Find(*comparator);
+
+	if(cacheEntry)
+		{
+		aSource.MergeL(*cacheEntry, aNewData);
+		}
+	else
+		{
+		// ok.. no record found.
+		//  So create a record populated with unknowns and merge the new data
+		//   into it.. this way only 1 merge function is needed
+		//    for determining whether we should notify with this data.
+
+		// This has to happen down in the collector as only the collector knows what an empty
+		//   record should look like (by adding technology specifics &c).
+		cacheEntry = aSource.ConstructEmptyRecordLC(aNewData.Id());
+
+		aSource.MergeL(*cacheEntry, aNewData);
+		iCache->AddL(cacheEntry);
+		CleanupStack::Pop(cacheEntry);
+		}
+
+	if(aAllowNotify)
+		{
+		NotifyOtherCollectorsOfChangeL(aSource,*cacheEntry);
+		}
+
+	ReportIfReadyL();
+
+	CleanupStack::PopAndDestroy(comparator);
+	}
+
+
+
+void CTierNotificationActivity::NotifyOtherCollectorsOfChangeL(CDataCollectorSession& aSource, CParameterSetContainer& aModifiedCacheEntry)
+	{
+	_TIER_LOG_2(_L8("CTierNotificationActivity %08x:\t" "NotifyOtherCollectorsOfChange()"),this);
+	for(TInt i=0; i<iCollectorSessions.Count() ; ++i)
+		{
+		// don't go round in circles
+		if(iCollectorSessions[i] != &aSource)
+			{
+			_TIER_LOG_3(_L8("\tNotifying collector session %d ( %08x ) of change"),i,iCollectorSessions[i]);
+			iCollectorSessions[i]->CacheModifiedL(aModifiedCacheEntry);
+			}
+		}
+	_TIER_LOG_PSC("After notifications: ",&aModifiedCacheEntry);
+	}
+
+
+//#define COMPARE_BY_ID_IN_PARAMETER_SET
+
+/*virtual*/ TBool CAccessPointIdComparator::Matches(const CParameterSetContainer& aRhs)
+	{
+#ifdef COMPARE_BY_ID_IN_PARAMETER_SET
+	const XAccessPointGenericParameterSet* lapgps = XAccessPointGenericParameterSet::FindInParamSetContainer(iToCompare);
+	const XAccessPointGenericParameterSet* rapgps = XAccessPointGenericParameterSet::FindInParamSetContainer(aRhs);
+	return(lapgps && rapgps && lapgps->AccessPointInfo() == rapgps->AccessPointInfo());
+#else
+	return (iToCompare.Id() == aRhs.Id());
+#endif
+	}
+
+
+
+
+// Tier Notification Cache
+
+/*static*/ CTierNotificationCache* CTierNotificationCache::NewL()
+	{
+	CTierNotificationCache* inst = new(ELeave) CTierNotificationCache();
+	CleanupStack::PushL(inst);
+	_TIER_LOG_2(_L8("CTierNotificationCache %08x:\t" "NewL()"), inst);
+	inst->SetCacheBundle(CGenericParameterBundle::NewL());
+	CleanupStack::Pop(inst);
+	return inst;
+	}
+
+
+CTierNotificationCache::~CTierNotificationCache()
+	{
+	_TIER_LOG_2(_L8("CTierNotificationCache %08x:\t" "~CTierNotificationCache()"),this);
+	delete iCacheBundle;
+	}
+
+
+CParameterSetContainer* CTierNotificationCache::Find(MEqualityComparator& aFinder)
+	{
+	_TIER_LOG_2(_L8("CTierNotificationCache %08x:\t" "Find()"),this);
+	CParameterSetContainer* entry;
+	TInt i=0;
+	while((entry = iCacheBundle->GetParamSetContainer(i++)) != NULL)
+		{
+		if(aFinder.Matches(*entry))
+			{
+			return entry;
+			}
+		}
+		return 0;
+	}
+
+
+void CTierNotificationCache::AddL(CParameterSetContainer* aItemToAdd)
+	{
+	_TIER_LOG_PSC("Adding to cache: ",aItemToAdd);
+	iCacheBundle->AddParamSetContainerL(*aItemToAdd);
+	}
+
+
+
+// Data Collector Session
+
+
+
+EXPORT_C CDataCollectorSession::CDataCollectorSession(MDataCollector* aCollector, CTierNotificationActivity& aActivity, TInt aSessionId)
+	:	iActivity(aActivity)
+	,	iCollector(aCollector)
+	,	iSessionId(aSessionId)
+	,	iIsLocked(ETrue)
+	{
+	iCollector->SetReceiver(*this);
+	}
+
+
+/*virtual*/
+CDataCollectorSession::~CDataCollectorSession()
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "~CDataCollectorSession()"),this);
+	delete iCollector;
+	}
+
+
+/*virtual*/
+void CDataCollectorSession::StartL()
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "StartL()"),this);
+	iCollector->StartL();
+	}
+
+
+/*virtual*/
+CParameterSetContainer* CDataCollectorSession::ConstructEmptyRecordLC(TUint32 aId /*=0*/)
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "ConstructEmptyRecordLC()"),this);
+	return iCollector->ConstructEmptyRecordLC(aId);
+	}
+
+/*virtual*/
+void CDataCollectorSession::MergeL(CParameterSetContainer& aCacheData, CParameterSetContainer& aNewData)
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "MergeL()"),this);
+
+	XNotificationCacheParameterSet* cacheParams = XNotificationCacheParameterSet::FindInParamSetContainer(aCacheData);
+	if(!cacheParams)
+		{
+		cacheParams = XNotificationCacheParameterSet::NewL(aCacheData);
+		}
+
+	// PREPARE THE CACHE FLAGS
+
+	// 1. InSet flag - whether the currently cached data matches the set of data to watch w.r.t. this data collector.
+	//
+	// We need to remember whether known data is currently in matching set for this data collector, and pass it into the merge fn.
+	//  This means doesn't have to run a "before" and "after" match of the query.
+	TBool inSet(cacheParams->InSet(iSessionId));
+
+	// 2. ToReport flag - whether we should report this change to client
+	//
+	// The collector's MergeL function decides on the basis of the InSet flag and the new data whether it's appropriate to report
+	//  the new data to the client.. This is an output variable only.. but let's set it to EFalse to be sure it's not garbage.
+	TBool toReport(EFalse);
+
+	_TIER_LOG_PSC("cache before merge: ",&aCacheData);
+	_TIER_LOG_PSC("new data: ",&aNewData);
+
+	iCollector->MergeL(aCacheData,aNewData,inSet,toReport);
+
+	cacheParams->InSet(iSessionId,inSet);
+	cacheParams->ToReport(iSessionId,toReport);
+
+	cacheParams->SetRunning(iSessionId); // we need to match this collector
+	cacheParams->SetReceived(iSessionId); // and we just received data for it
+
+	_TIER_LOG_PSC("cache after merge: ",&aCacheData);
+	}
+
+
+// Called when cache is modified.. to possibly kickstart further discovery.
+// 	be VERY careful if this leads to calling DataReceivedL as potentially
+//   this could result in an infinite recursion
+//    (DataReceived->AddToCache->NotifyOtherCollectors->CacheModified->DataReceived->....)
+/*virtual*/
+void CDataCollectorSession::CacheModifiedL(CParameterSetContainer& aModifiedEntry)
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "CacheModifiedL()"),this);
+	TBool isRunning=EFalse; // collector sets this if it decides the notification
+					// represents an AP it is collecting information about..
+	iCollector->CacheModifiedL(aModifiedEntry,isRunning);
+	if(isRunning)
+		{
+		XNotificationCacheParameterSet* cacheParams = XNotificationCacheParameterSet::FindInParamSetContainer(aModifiedEntry);
+		__ASSERT_DEBUG(cacheParams, User::Panic(KSpecAssert_ESockCrPrvTNotAC, 7));
+		// this collector is active in deciding that the AP is ready to report
+		//   i.e. it is collecting data about this AP and it won't unlock til
+		//      it has received data
+		cacheParams->SetRunning(iSessionId);
+		}
+	}
+
+
+
+// From MCollectedDataReceiver : called by data collector.
+//   Takes ownership of aNewData and deletes it when finished
+/*virtual*/
+void CDataCollectorSession::DataReceivedL(CParameterSetContainer* aNewData, TBool aAllowNotify)
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "DataReceivedL()"),this);
+	// take ownership straight away
+	CleanupStack::PushL(aNewData);
+	iActivity.DataReceivedL(*this,*aNewData,aAllowNotify);
+	CleanupStack::PopAndDestroy(aNewData);
+	}
+
+// From MCollectedDataReceiver : called by data collector
+/*virtual*/
+void CDataCollectorSession::Lock()
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "Lock()"),this);
+	iIsLocked = ETrue;
+	}
+
+// From MCollectedDataReceiver : called by data collector
+/*virtual*/
+void CDataCollectorSession::Unlock()
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "Unlock()"),this);
+	iIsLocked = EFalse;
+
+	// release notifications if necessary
+	TRAP_IGNORE(iActivity.ReportIfReadyL());
+	}
+
+// From MCollectedDataReceiver : called by data collector
+/*virtual*/
+TBool CDataCollectorSession::IsLocked() const
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "IsLocked()"),this);
+	return iIsLocked;
+	}
+
+
+// From MCollectedDataReceiver : called by data collector
+/*virtual*/
+void CDataCollectorSession::ShutdownFinished()
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "ShutdownFinished()"),this);
+
+	delete iCollector;
+	iCollector=0;
+
+	iActivity.ShutdownFinished();
+	}
+
+// From MCollectedDataReceiver : called by data collector when it wants to do
+//           its own cleanup and destruction
+/*virtual*/
+void CDataCollectorSession::Detach()
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "Detach()"),this);
+
+	iCollector=0;
+
+	iActivity.ShutdownFinished();
+	}
+
+
+void CDataCollectorSession::Error(TInt aErr)
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "Error()"),this);
+
+	iActivity.InitiateShutdown(aErr);
+	}
+
+
+
+/*virtual*/
+TBool CDataCollectorSession::HasShutDown()
+	{
+	return iCollector?ETrue:EFalse;
+	}
+
+
+/*virtual*/
+void CDataCollectorSession::Shutdown()
+	{
+	_TIER_LOG_2(_L8("CDataCollectorSession %08x:\t" "Shutdown()"),this);
+	iCollector->Shutdown();
+	}
+