/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.collect.Sets;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.target.resolver.IdentifierResolver;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.util.PoolExecutor;
import com.opengamma.util.function.BinaryOperator;
/**
* Standard implementation of a {@link ComputationTargetSpecificationResolver}.
* <p>
* Note that this is a fairly cheap operation; looking up the resolver and calling that. It should not normally be necessary to provide any caching on top of a specification resolver - if a
* requirement is being resolved regularly for the same version/correction then there is probably something wrong elsewhere. If a resolver implementation is costly (for example querying an underlying
* data source) then that is where the caching should lie.
*/
public class DefaultComputationTargetSpecificationResolver implements ComputationTargetSpecificationResolver {
private abstract static class SpecificationResolver {
public abstract ComputationTargetSpecification resolveRequirement(ComputationTargetRequirement requirement, VersionCorrection versionCorrection);
public abstract void resolveRequirements(Collection<ComputationTargetRequirement> requirements, VersionCorrection versionCorrection,
Map<ComputationTargetReference, ComputationTargetSpecification> result);
public abstract ComputationTargetSpecification resolveObjectId(ComputationTargetReference parent, ObjectId identifier, VersionCorrection versionCorrection);
public abstract void resolveSpecifications(Collection<ComputationTargetSpecification> specifications, VersionCorrection versionCorrection,
Map<ComputationTargetReference, ComputationTargetSpecification> result);
}
private static class SingleSpecificationResolver extends SpecificationResolver {
private final ComputationTargetType _type;
private final IdentifierResolver _resolver;
public SingleSpecificationResolver(final ComputationTargetType type, final IdentifierResolver resolver) {
_type = type;
_resolver = resolver;
}
private ComputationTargetSpecification resolved(final ComputationTargetReference parent, final UniqueId uid) {
if (parent != null) {
return parent.containing(_type, uid);
} else {
return new ComputationTargetSpecification(_type, uid);
}
}
@Override
public ComputationTargetSpecification resolveRequirement(final ComputationTargetRequirement requirement, final VersionCorrection versionCorrection) {
final UniqueId uid = _resolver.resolveExternalId(requirement.getIdentifiers(), versionCorrection);
if (uid != null) {
return resolved(requirement.getParent(), uid);
} else {
return null;
}
}
@Override
public void resolveRequirements(final Collection<ComputationTargetRequirement> requirements, final VersionCorrection versionCorrection,
final Map<ComputationTargetReference, ComputationTargetSpecification> result) {
final Set<ExternalIdBundle> identifiers = Sets.newHashSetWithExpectedSize(requirements.size());
for (ComputationTargetRequirement requirement : requirements) {
identifiers.add(requirement.getIdentifiers());
}
final Map<ExternalIdBundle, UniqueId> resolved = _resolver.resolveExternalIds(identifiers, versionCorrection);
for (ComputationTargetRequirement requirement : requirements) {
final UniqueId uid = resolved.get(requirement.getIdentifiers());
if (uid != null) {
result.put(requirement, resolved(requirement.getParent(), uid));
}
}
}
@Override
public ComputationTargetSpecification resolveObjectId(final ComputationTargetReference parent, final ObjectId identifier, final VersionCorrection versionCorrection) {
final UniqueId uid = _resolver.resolveObjectId(identifier, versionCorrection);
if (uid != null) {
return resolved(parent, uid);
} else {
return null;
}
}
@Override
public void resolveSpecifications(final Collection<ComputationTargetSpecification> specifications, final VersionCorrection versionCorrection,
final Map<ComputationTargetReference, ComputationTargetSpecification> result) {
final Set<ObjectId> identifiers = Sets.newHashSetWithExpectedSize(specifications.size());
for (ComputationTargetSpecification specification : specifications) {
identifiers.add(specification.getUniqueId().getObjectId());
}
final Map<ObjectId, UniqueId> resolved = _resolver.resolveObjectIds(identifiers, versionCorrection);
for (ComputationTargetSpecification specification : specifications) {
final UniqueId uid = resolved.get(specification.getUniqueId().getObjectId());
if (uid != null) {
result.put(specification, resolved(specification.getParent(), uid));
}
}
}
}
private static class FoldedSpecificationResolver extends SpecificationResolver {
private final SpecificationResolver _a;
private final SpecificationResolver _b;
public FoldedSpecificationResolver(final SpecificationResolver a, final SpecificationResolver b) {
_a = a;
_b = b;
}
@Override
public ComputationTargetSpecification resolveRequirement(final ComputationTargetRequirement requirement, final VersionCorrection versionCorrection) {
final ComputationTargetSpecification a = _a.resolveRequirement(requirement, versionCorrection);
if (a != null) {
return a;
}
return _b.resolveRequirement(requirement, versionCorrection);
}
@Override
public void resolveRequirements(final Collection<ComputationTargetRequirement> requirements, final VersionCorrection versionCorrection,
final Map<ComputationTargetReference, ComputationTargetSpecification> result) {
int remaining = requirements.size() + result.size();
_a.resolveRequirements(requirements, versionCorrection, result);
remaining -= result.size();
if (remaining > 0) {
final List<ComputationTargetRequirement> pending = new ArrayList<ComputationTargetRequirement>(remaining);
for (ComputationTargetRequirement requirement : requirements) {
if (!result.containsKey(requirement)) {
pending.add(requirement);
}
}
_b.resolveRequirements(pending, versionCorrection, result);
}
}
@Override
public ComputationTargetSpecification resolveObjectId(final ComputationTargetReference parent, final ObjectId identifier, final VersionCorrection versionCorrection) {
final ComputationTargetSpecification a = _a.resolveObjectId(parent, identifier, versionCorrection);
if (a != null) {
return a;
}
return _b.resolveObjectId(parent, identifier, versionCorrection);
}
@Override
public void resolveSpecifications(final Collection<ComputationTargetSpecification> specifications, final VersionCorrection versionCorrection,
final Map<ComputationTargetReference, ComputationTargetSpecification> result) {
int remaining = specifications.size() + result.size();
_a.resolveSpecifications(specifications, versionCorrection, result);
remaining -= result.size();
if (remaining > 0) {
final List<ComputationTargetSpecification> pending = new ArrayList<ComputationTargetSpecification>(remaining);
for (ComputationTargetSpecification specification : specifications) {
if (!result.containsKey(specification)) {
pending.add(specification);
}
}
_b.resolveSpecifications(pending, versionCorrection, result);
}
}
}
private static final BinaryOperator<SpecificationResolver> s_fold = new BinaryOperator<SpecificationResolver>() {
@Override
public SpecificationResolver apply(final SpecificationResolver a, final SpecificationResolver b) {
return new FoldedSpecificationResolver(a, b);
}
};
private final ComputationTargetTypeMap<SpecificationResolver> _resolve = new ComputationTargetTypeMap<SpecificationResolver>(s_fold);
public void addResolver(final ComputationTargetType type, final IdentifierResolver strategy) {
_resolve.put(type, new SingleSpecificationResolver(type, strategy));
}
@Override
public ComputationTargetSpecification getTargetSpecification(final ComputationTargetReference reference, final VersionCorrection versionCorrection) {
return atVersionCorrection(versionCorrection).getTargetSpecification(reference);
}
@Override
public Map<ComputationTargetReference, ComputationTargetSpecification> getTargetSpecifications(final Set<ComputationTargetReference> references, final VersionCorrection versionCorrection) {
return atVersionCorrection(versionCorrection).getTargetSpecifications(references);
}
@Override
public AtVersionCorrection atVersionCorrection(final VersionCorrection versionCorrection) {
return new AtVersionCorrection() {
private PoolExecutor.Service<Void> createService() {
final PoolExecutor executor = PoolExecutor.instance();
if (executor != null) {
return executor.createService(null);
} else {
return null;
}
}
private final ComputationTargetReferenceVisitor<ComputationTargetSpecification> _getTargetSpecification =
new ComputationTargetReferenceVisitor<ComputationTargetSpecification>() {
@Override
public ComputationTargetSpecification visitComputationTargetRequirement(final ComputationTargetRequirement requirement) {
final SpecificationResolver resolver = _resolve.get(requirement.getType());
if (resolver != null) {
return resolver.resolveRequirement(requirement, versionCorrection);
} else {
return null;
}
}
@Override
public ComputationTargetSpecification visitComputationTargetSpecification(final ComputationTargetSpecification specification) {
final UniqueId uid = specification.getUniqueId();
if ((uid != null) && uid.isLatest()) {
final SpecificationResolver resolver = _resolve.get(specification.getType());
if (resolver != null) {
return resolver.resolveObjectId(specification.getParent(), uid.getObjectId(), versionCorrection);
} else {
return specification;
}
} else {
return specification;
}
}
};
@Override
public ComputationTargetSpecification getTargetSpecification(final ComputationTargetReference reference) {
return reference.accept(_getTargetSpecification);
}
@Override
public Map<ComputationTargetReference, ComputationTargetSpecification> getTargetSpecifications(final Set<ComputationTargetReference> references) {
final Map<ComputationTargetReference, ComputationTargetSpecification> result = new ConcurrentHashMap<ComputationTargetReference, ComputationTargetSpecification>();
final Map<ComputationTargetType, Set<ComputationTargetRequirement>> requirementByType = new HashMap<ComputationTargetType, Set<ComputationTargetRequirement>>();
final Map<ComputationTargetType, Set<ComputationTargetSpecification>> specificationByType = new HashMap<ComputationTargetType, Set<ComputationTargetSpecification>>();
final ComputationTargetReferenceVisitor<Void> visitor = new ComputationTargetReferenceVisitor<Void>() {
@Override
public Void visitComputationTargetRequirement(final ComputationTargetRequirement requirement) {
Set<ComputationTargetRequirement> requirements = requirementByType.get(requirement.getType());
if (requirements == null) {
if (_resolve.get(requirement.getType()) != null) {
requirements = new HashSet<ComputationTargetRequirement>();
requirementByType.put(requirement.getType(), requirements);
// Add to the bulk resolution set
requirements.add(requirement);
} else {
// No resolver for this type
requirementByType.put(requirement.getType(), Collections.<ComputationTargetRequirement>emptySet());
}
} else {
if (!requirements.isEmpty()) {
// Add to the bulk resolution set. If the set is empty, we've cached the "no resolver" status
requirements.add(requirement);
}
}
return null;
}
@Override
public Void visitComputationTargetSpecification(final ComputationTargetSpecification specification) {
if ((specification.getUniqueId() != null) && (specification.getUniqueId().isLatest())) {
Set<ComputationTargetSpecification> specifications = specificationByType.get(specification.getType());
if (specifications == null) {
if (_resolve.get(specification.getType()) != null) {
specifications = new HashSet<ComputationTargetSpecification>();
specificationByType.put(specification.getType(), specifications);
// Add to the bulk resolution set
specifications.add(specification);
} else {
// No resolver for this type
specificationByType.put(specification.getType(), Collections.<ComputationTargetSpecification>emptySet());
result.put(specification, specification);
}
} else {
if (specifications.isEmpty()) {
// No resolver for this type
result.put(specification, specification);
} else {
// Add to the bulk resolution set
specifications.add(specification);
}
}
} else {
// Already strictly versioned
result.put(specification, specification);
}
return null;
}
};
for (final ComputationTargetReference reference : references) {
reference.accept(visitor);
}
final PoolExecutor.Service<Void> jobs = createService();
// TODO: sort the target types - some resolvers will cause caching behavior that will help others out (e.g. resolving Portfolio OID will cache all component Position OID/UIDs).
// TODO: should there be a threshold for single vs bulk - e.g. are two calls in succession quicker than the map/set operations?
for (final Map.Entry<ComputationTargetType, Set<ComputationTargetRequirement>> entry : requirementByType.entrySet()) {
switch (entry.getValue().size()) {
case 0:
// No resolver for this type
break;
case 1: {
// Single item
if (jobs != null) {
jobs.execute(new Runnable() {
@Override
public void run() {
final ComputationTargetRequirement requirement = entry.getValue().iterator().next();
final ComputationTargetSpecification specification = _resolve.get(entry.getKey()).resolveRequirement(requirement, versionCorrection);
if (specification != null) {
synchronized (result) {
result.put(requirement, specification);
}
}
}
});
} else {
final ComputationTargetRequirement requirement = entry.getValue().iterator().next();
final ComputationTargetSpecification specification = _resolve.get(entry.getKey()).resolveRequirement(requirement, versionCorrection);
if (specification != null) {
result.put(requirement, specification);
}
}
break;
}
default: {
// Bulk lookup
if (jobs != null) {
jobs.execute(new Runnable() {
@Override
public void run() {
_resolve.get(entry.getKey()).resolveRequirements(entry.getValue(), versionCorrection, result);
}
});
} else {
_resolve.get(entry.getKey()).resolveRequirements(entry.getValue(), versionCorrection, result);
}
break;
}
}
}
for (final Map.Entry<ComputationTargetType, Set<ComputationTargetSpecification>> entry : specificationByType.entrySet()) {
switch (entry.getValue().size()) {
case 0:
// No resolver for this type
break;
case 1: {
// Single item
if (jobs != null) {
jobs.execute(new Runnable() {
@Override
public void run() {
final ComputationTargetSpecification specification = entry.getValue().iterator().next();
final ComputationTargetSpecification resolved = _resolve.get(entry.getKey()).resolveObjectId(specification.getParent(), specification.getUniqueId().getObjectId(),
versionCorrection);
if (resolved != null) {
result.put(specification, resolved);
}
}
});
} else {
final ComputationTargetSpecification specification = entry.getValue().iterator().next();
final ComputationTargetSpecification resolved = _resolve.get(entry.getKey()).resolveObjectId(specification.getParent(), specification.getUniqueId().getObjectId(), versionCorrection);
if (resolved != null) {
result.put(specification, resolved);
}
}
break;
}
default: {
// Bulk lookup
if (jobs != null) {
jobs.execute(new Runnable() {
@Override
public void run() {
_resolve.get(entry.getKey()).resolveSpecifications(entry.getValue(), versionCorrection, result);
}
});
} else {
_resolve.get(entry.getKey()).resolveSpecifications(entry.getValue(), versionCorrection, result);
}
break;
}
}
}
if (jobs != null) {
try {
jobs.join();
} catch (InterruptedException e) {
throw new OpenGammaRuntimeException("Interrupted", e);
}
}
return result;
}
};
}
}