/*
* RHQ Management Platform
* Copyright (C) 2005-2010 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.enterprise.server.resource.disambiguation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.rhq.core.domain.resource.composite.DisambiguationReport;
import org.rhq.core.util.IntExtractor;
import org.rhq.enterprise.server.resource.disambiguation.MutableDisambiguationReport.Resource;
import org.rhq.enterprise.server.resource.disambiguation.MutableDisambiguationReport.ResourceType;
/**
* This is basically a helper class that provides the disambiguation method.
* It is intended to be used in an SLSB context.
*
* @author Lukas Krejci
*/
public class Disambiguator {
/**
* The maximum depth of the resource tree.
*/
public static final int MAXIMUM_DISAMBIGUATED_TREE_DEPTH = 7;
private static final String PARENT_INFO_QUERY;
static {
StringBuilder selectBuilder = new StringBuilder(
"SELECT r0.id, r0.name, r0.resourceType.id, r0.resourceType.name, r0.resourceType.plugin, r0.resourceType.singleton");
StringBuilder fromBuilder = new StringBuilder("FROM Resource r0");
for (int i = 1; i <= MAXIMUM_DISAMBIGUATED_TREE_DEPTH; ++i) {
int pi = i - 1;
selectBuilder.append(", r").append(i).append(".id");
selectBuilder.append(", r").append(i).append(".name");
selectBuilder.append(", rt").append(i).append(".id");
selectBuilder.append(", rt").append(i).append(".name");
selectBuilder.append(", rt").append(i).append(".plugin");
selectBuilder.append(", rt").append(i).append(".singleton");
fromBuilder.append(" left join r").append(pi).append(".parentResource r").append(i);
fromBuilder.append(" left join r").append(i).append(".resourceType rt").append(i);
}
fromBuilder.append(" WHERE r0.id IN (:resourceIds)");
PARENT_INFO_QUERY = selectBuilder.append(" ").append(fromBuilder).toString();
}
private Disambiguator() {
}
/**
* Given a list of results, this method produces an object decorates the provided original results
* with data needed to disambiguate the results with respect to resource names, their types and ancestory.
* <p>
* The disambiguation result contains information on what types of information are needed to make the resources
* in the original result unambiguous and contains the decorated original data in the same order as the
* supplied result list.
* <p>
* The objects in results do not necessarily need to correspond to a resource. In case of such objects,
* the resourceIdExtractor should return 0. In the resulting report such objects will still be wrapped
* in a {@link DisambiguationReport} but the parent list will be empty and resource type and plugin name will
* be null.
*
* @see ResourceNamesDisambiguationResult
*
* @param <T> the type of the result elements
* @param results the results to disambiguate
* @param disambiguationUpdateStrategy how is the disambiguation info going to be applied to the results.
* @param resourceIdExtractor an object able to extract resource id from an instance of type parameter.
* @param entityManager an entityManager to be used to access the database
* @param duplicateTypeNames the list of type names that are ambiguous without plugin spec
* @return the disambiguation result
*/
public static <T> List<DisambiguationReport<T>> disambiguate(List<T> results,
DisambiguationUpdateStrategy disambiguationUpdateStrategy, IntExtractor<? super T> extractor,
EntityManager entityManager, List<String> duplicateTypeNames) {
if (results.isEmpty()) {
return new ArrayList<DisambiguationReport<T>>();
}
//we can't assume the ordering of the provided results and the disambiguation query results
//will be the same.
//this list contains the resulting reports in the same order as the original results
List<MutableDisambiguationReport<T>> reports = new ArrayList<MutableDisambiguationReport<T>>(results.size());
//this maps the reports to resourceIds. More than one report can correspond to a single
//resource id. The reports in this map are the same instances as in the reports list.
Map<Integer, List<MutableDisambiguationReport<T>>> reportsByResourceId = new HashMap<Integer, List<MutableDisambiguationReport<T>>>();
for (T r : results) {
int resourceId = extractor.extract(r);
MutableDisambiguationReport<T> value = new MutableDisambiguationReport<T>();
value.original = r;
if (resourceId > 0) {
List<MutableDisambiguationReport<T>> correspondingResults = reportsByResourceId.get(resourceId);
if (correspondingResults == null) {
correspondingResults = new ArrayList<MutableDisambiguationReport<T>>();
reportsByResourceId.put(resourceId, correspondingResults);
}
correspondingResults.add(value);
}
reports.add(value);
}
//check that we still have something to disambiguate
if (reportsByResourceId.size() > 0) {
//k, now let's construct the JPQL query to get the parents and type infos...
Query parentsQuery = entityManager.createQuery(PARENT_INFO_QUERY);
parentsQuery.setParameter("resourceIds", reportsByResourceId.keySet());
//ok, now I will obtain all the information about the parents and types
//using the above defined JPQL query.
//I will partition the resulting reports by resource name.to create groups of
//resources that are "mutually ambiguous". Because each such group potenitally
//requires different level of disambiguation, I will then process them individually.
ReportPartitions<T> partitionedReports = new ReportPartitions<T>(DisambiguationPolicy
.getUniqueNamePolicy(disambiguationUpdateStrategy, duplicateTypeNames));
@SuppressWarnings("unchecked")
List<Object[]> parentsResults = (List<Object[]>) parentsQuery.getResultList();
for (Object[] parentsResult : parentsResults) {
List<MutableDisambiguationReport.Resource> parents = new ArrayList<MutableDisambiguationReport.Resource>(
MAXIMUM_DISAMBIGUATED_TREE_DEPTH);
Integer resourceId = (Integer) parentsResult[0];
String resourceName = (String) parentsResult[1];
Integer typeId = (Integer) parentsResult[2];
String typeName = (String) parentsResult[3];
String pluginName = (String) parentsResult[4];
Boolean singleton = (Boolean) parentsResult[5];
MutableDisambiguationReport.ResourceType resourceType = new MutableDisambiguationReport.ResourceType();
resourceType.id = typeId;
resourceType.name = typeName;
resourceType.plugin = pluginName;
resourceType.singleton = singleton;
MutableDisambiguationReport.Resource resource = new MutableDisambiguationReport.Resource();
resource.id = resourceId;
resource.name = resourceName;
resource.resourceType = resourceType;
for (int i = 0; i < MAXIMUM_DISAMBIGUATED_TREE_DEPTH; ++i) {
Integer parentId = (Integer) parentsResult[6 + 6 * i];
if (parentId == null)
break;
String parentName = (String) parentsResult[6 + 6 * i + 1];
Integer parentTypeId = (Integer) parentsResult[6 + 6 * i + 2];
String parentType = (String) parentsResult[6 + 6 * i + 3];
String parentPlugin = (String) parentsResult[6 + 6 * i + 4];
Boolean parentSingleton = (Boolean) parentsResult[6 + 6 * i + 5];
MutableDisambiguationReport.ResourceType type = new MutableDisambiguationReport.ResourceType();
type.id = parentTypeId;
type.name = parentType;
type.plugin = parentPlugin;
type.singleton = parentSingleton;
MutableDisambiguationReport.Resource parent = new MutableDisambiguationReport.Resource();
parent.id = parentId;
parent.name = parentName;
parent.resourceType = type;
parents.add(parent);
}
//update all the reports that correspond to this resourceId
for (MutableDisambiguationReport<T> report : reportsByResourceId.get(resourceId)) {
report.resource = resource.clone();
report.parents = deepCopy(parents);
partitionedReports.put(report);
}
}
//ok, now I have the reports partitioned by resource name. let's go through each partition
//and figure out the disambiguation needed for it.
List<ReportPartitions<T>> ambiguousSubPartitions = new ArrayList<ReportPartitions<T>>();
if (!disambiguationUpdateStrategy.partitionFurther(partitionedReports)) {
updateResourcesInPartitions(disambiguationUpdateStrategy, partitionedReports.getDisambiguationPolicy(), partitionedReports.getAllPartitions());
} else {
if (!partitionedReports.isPartitionsUnique()) {
ambiguousSubPartitions.add(partitionedReports);
} else {
repartitionUnique(partitionedReports, disambiguationUpdateStrategy, ambiguousSubPartitions);
}
}
while (ambiguousSubPartitions.size() > 0) {
Iterator<ReportPartitions<T>> subPartitionIterator = ambiguousSubPartitions.iterator();
List<ReportPartitions<T>> newAmbiguousPartitions = new ArrayList<ReportPartitions<T>>();
while (subPartitionIterator.hasNext()) {
ReportPartitions<T> subPartition = subPartitionIterator.next();
if (disambiguationUpdateStrategy.partitionFurther(subPartition)) {
repartitionUnique(subPartition, disambiguationUpdateStrategy, newAmbiguousPartitions);
for (List<MutableDisambiguationReport<T>> partitionReports : subPartition.getAmbiguousPartitions()) {
ReportPartitions<T> replacementSubpartition = new ReportPartitions<T>(subPartition
.getDisambiguationPolicy().getNext());
replacementSubpartition.putAll(partitionReports);
if (!replacementSubpartition.isPartitionsUnique()) {
newAmbiguousPartitions.add(replacementSubpartition);
} else {
repartitionUnique(replacementSubpartition, disambiguationUpdateStrategy, newAmbiguousPartitions);
}
}
} else {
updateResourcesInPartitions(disambiguationUpdateStrategy, subPartition.getDisambiguationPolicy(), subPartition.getAllPartitions());
}
subPartitionIterator.remove();
}
for (ReportPartitions<T> newPartition : newAmbiguousPartitions) {
ambiguousSubPartitions.add(newPartition);
}
}
}
List<DisambiguationReport<T>> resolution = new ArrayList<DisambiguationReport<T>>(results.size());
for (MutableDisambiguationReport<T> report : reports) {
resolution.add(report.getReport());
}
return resolution;
}
private static <T> void repartitionUnique(ReportPartitions<T> partitions, DisambiguationUpdateStrategy updateStrategy, List<ReportPartitions<T>> ambigousPartitions) {
while (true) {
//try to repartition
DisambiguationPolicy repartitionPolicy = partitions.getDisambiguationPolicy().getNextRepartitioningPolicy();
if (repartitionPolicy != null) {
//ok, we have a new policy to try... let's see if it makes any difference.
partitions = new ReportPartitions<T>(repartitionPolicy, partitions.getUniquePartitions());
//bail out if we have partitions that are not unique
if (!partitions.isPartitionsUnique()) {
ambigousPartitions.add(partitions);
return;
}
} else {
//ok, there is no other repartitioning policy that we can try.
//Let's update the reports in the unique partitions...
updateResourcesInPartitions(updateStrategy, partitions.getDisambiguationPolicy(), partitions.getUniquePartitions());
return;
}
}
}
private static <T> void updateResourcesInPartitions(DisambiguationUpdateStrategy strategy, DisambiguationPolicy policy, List<List<MutableDisambiguationReport<T>>> partitions) {
for (List<MutableDisambiguationReport<T>> partition : partitions) {
for (MutableDisambiguationReport<T> report : partition) {
strategy.update(policy, report);
}
}
}
private static List<Resource> deepCopy(List<Resource> original) {
ArrayList<Resource> copy = new ArrayList<Resource>();
for (Resource o : original) {
copy.add(o.clone());
}
return copy;
}
}