*/
protected BasicGetNodeCommand getNode( Path path ) throws RepositorySourceException {
// Check the cache first ...
final ExecutionContext context = getExecutionContext();
RepositoryConnection cacheConnection = getConnectionToCache();
BasicGetNodeCommand fromCache = new BasicGetNodeCommand(path);
cacheConnection.execute(context, fromCache);
// Look at the cache results from the cache for problems, or if found a plan in the cache look
// at the contributions. We'll be putting together the set of source names for which we need to
// get the contributions.
Set<String> sourceNames = null;
List<Contribution> contributions = new LinkedList<Contribution>();
if (fromCache.hasError()) {
Throwable error = fromCache.getError();
if (!(error instanceof PathNotFoundException)) return fromCache;
// The path was not found in the cache, so since we don't know whether the ancestors are federated
// from multiple source nodes, we need to populate the cache starting with the lowest ancestor
// that already exists in the cache.
PathNotFoundException notFound = (PathNotFoundException)fromCache.getError();
Path lowestExistingAncestor = notFound.getLowestAncestorThatDoesExist();
Path ancestor = path.getParent();
if (!ancestor.equals(lowestExistingAncestor)) {
// Load the nodes along the path below the existing ancestor, down to (but excluding) the desired path
Path pathToLoad = path.getParent();
while (!pathToLoad.equals(lowestExistingAncestor)) {
loadContributionsFromSources(pathToLoad, null, contributions); // sourceNames may be null or empty
FederatedNode mergedNode = createFederatedNode(null, pathToLoad, contributions, true);
if (mergedNode == null) {
// No source had a contribution ...
I18n msg = FederationI18n.nodeDoesNotExistAtPath;
fromCache.setError(new PathNotFoundException(path, ancestor, msg.text(path, ancestor)));
return fromCache;
}
contributions.clear();
// Move to the next child along the path ...
pathToLoad = pathToLoad.getParent();
}
}
// At this point, all ancestors exist ...
} else {
// There is no error, so look for the merge plan ...
MergePlan mergePlan = getMergePlan(fromCache);
if (mergePlan != null) {
// We found the merge plan, so check whether it's still valid ...
final DateTime now = getCurrentTimeInUtc();
if (mergePlan.isExpired(now)) {
// It is still valid, so check whether any contribution is from a non-existant projection ...
for (Contribution contribution : mergePlan) {
if (!this.sourceNames.contains(contribution.getSourceName())) {
// TODO: Record that the cached contribution is from a source that is no longer in this repository
}
}
return fromCache;
}
// At least one of the contributions is expired, so go through the contributions and place
// the valid contributions in the 'contributions' list; any expired contribution
// needs to be loaded by adding the name to the 'sourceNames'
if (mergePlan.getContributionCount() > 0) {
sourceNames = new HashSet<String>(sourceNames);
for (Contribution contribution : mergePlan) {
if (!contribution.isExpired(now)) {
sourceNames.remove(contribution.getSourceName());
contributions.add(contribution);
}
}
}
}
}
// Get the contributions from the sources given their names ...
loadContributionsFromSources(path, sourceNames, contributions); // sourceNames may be null or empty
FederatedNode mergedNode = createFederatedNode(fromCache, path, contributions, true);
if (mergedNode == null) {
// No source had a contribution ...
Path ancestor = path.getParent();
I18n msg = FederationI18n.nodeDoesNotExistAtPath;
fromCache.setError(new PathNotFoundException(path, ancestor, msg.text(path, ancestor)));
return fromCache;
}
return mergedNode;
}