blob: 4f1751d72194f9e99e10709f353285eb501a8e79 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ambari.server.controller.internal;
import org.apache.ambari.server.controller.spi.ProviderModule;
import org.apache.ambari.server.controller.spi.*;
import org.apache.ambari.server.controller.utilities.PredicateBuilder;
import org.apache.ambari.server.controller.utilities.PredicateHelper;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
/**
* Default cluster controller implementation.
*/
public class ClusterControllerImpl implements ClusterController {
private final static Logger LOG =
LoggerFactory.getLogger(ClusterControllerImpl.class);
/**
* Module of providers for this controller.
*/
private final ProviderModule providerModule;
/**
* Map of resource providers keyed by resource type.
*/
private final Map<Resource.Type, ResourceProvider> resourceProviders =
new HashMap<Resource.Type, ResourceProvider>();
/**
* Map of property provider lists keyed by resource type.
*/
private final Map<Resource.Type, List<PropertyProvider>> propertyProviders =
new HashMap<Resource.Type, List<PropertyProvider>>();
/**
* Map of schemas keyed by resource type.
*/
private final Map<Resource.Type, Schema> schemas =
new HashMap<Resource.Type, Schema>();
/**
* Resource comparator.
*/
private final ResourceComparator comparator = new ResourceComparator();
/**
* Predicate evaluator
*/
private static final DefaultResourcePredicateEvaluator
DEFAULT_RESOURCE_PREDICATE_EVALUATOR =
new DefaultResourcePredicateEvaluator();
// ----- Constructors ------------------------------------------------------
public ClusterControllerImpl(ProviderModule providerModule) {
this.providerModule = providerModule;
}
// ----- ClusterController -------------------------------------------------
@Override
public Set<Resource> getResources(Resource.Type type, Request request, Predicate predicate)
throws UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException, SystemException {
Set<Resource> resources;
ResourceProvider provider = ensureResourceProvider(type);
ensurePropertyProviders(type);
if (provider == null) {
resources = Collections.emptySet();
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Using resource provider "
+ provider.getClass().getName()
+ " for request type " + type.toString());
}
// make sure that the providers can satisfy the request
checkProperties(type, request, predicate);
// get the resources
resources = provider.getResources(request, predicate);
}
return resources;
}
@Override
public Set<Resource> populateResources(Resource.Type type,
Set<Resource> resources,
Request request,
Predicate predicate) throws SystemException {
Set<Resource> keepers = resources;
for (PropertyProvider propertyProvider : propertyProviders.get(type)) {
if (providesRequestProperties(propertyProvider, request, predicate)) {
keepers = propertyProvider.populateResources(keepers, request, predicate);
}
}
return keepers;
}
@Override
public Iterable<Resource> getIterable(Resource.Type type, Set<Resource> providerResources,
Request request, Predicate predicate)
throws NoSuchParentResourceException, UnsupportedPropertyException, NoSuchResourceException, SystemException {
return getPage(type, providerResources, request, predicate, null).getIterable();
}
@Override
public PageResponse getPage(Resource.Type type, Set<Resource> providerResources,
Request request, Predicate predicate, PageRequest pageRequest)
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException {
Set<Resource> resources;
ResourceProvider provider = ensureResourceProvider(type);
ResourcePredicateEvaluator evaluator = provider instanceof ResourcePredicateEvaluator ?
(ResourcePredicateEvaluator) provider : DEFAULT_RESOURCE_PREDICATE_EVALUATOR;
if (!providerResources.isEmpty()) {
Comparator<Resource> resourceComparator = pageRequest == null || pageRequest.getComparator() == null ?
comparator : pageRequest.getComparator();
TreeSet<Resource> sortedResources = new TreeSet<Resource>(resourceComparator);
sortedResources.addAll(providerResources);
if (pageRequest != null) {
switch (pageRequest.getStartingPoint()) {
case Beginning:
return getPageFromOffset(pageRequest.getPageSize(), 0,
sortedResources, request, predicate, evaluator);
case End:
return getPageToOffset(pageRequest.getPageSize(), -1,
sortedResources, request, predicate, evaluator);
case OffsetStart:
return getPageFromOffset(pageRequest.getPageSize(),
pageRequest.getOffset(), sortedResources, request, predicate, evaluator);
case OffsetEnd:
return getPageToOffset(pageRequest.getPageSize(),
pageRequest.getOffset(), sortedResources, request, predicate, evaluator);
// TODO : need to support the following cases for pagination
// case PredicateStart:
// case PredicateEnd:
}
}
resources = sortedResources;
} else {
resources = providerResources;
}
return new PageResponseImpl(new ResourceIterable(resources, request, predicate, evaluator),
0, null, null);
}
@Override
public Schema getSchema(Resource.Type type) {
Schema schema;
synchronized (schemas) {
schema = schemas.get(type);
if (schema == null) {
schema = new SchemaImpl(ensureResourceProvider(type));
schemas.put(type, schema);
}
}
return schema;
}
@Override
public RequestStatus createResources(Resource.Type type, Request request)
throws UnsupportedPropertyException,
SystemException,
ResourceAlreadyExistsException,
NoSuchParentResourceException {
ResourceProvider provider = ensureResourceProvider(type);
if (provider != null) {
checkProperties(type, request, null);
return provider.createResources(request);
}
return null;
}
@Override
public RequestStatus updateResources(Resource.Type type, Request request, Predicate predicate)
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException {
ResourceProvider provider = ensureResourceProvider(type);
if (provider != null) {
if (!checkProperties(type, request, predicate)) {
predicate = resolvePredicate(type, predicate);
if (predicate == null) {
return null;
}
}
return provider.updateResources(request, predicate);
}
return null;
}
@Override
public RequestStatus deleteResources(Resource.Type type, Predicate predicate)
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException {
ResourceProvider provider = ensureResourceProvider(type);
if (provider != null) {
if (!checkProperties(type, null, predicate)) {
predicate = resolvePredicate(type, predicate);
if (predicate == null) {
return null;
}
}
return provider.deleteResources(predicate);
}
return null;
}
// ----- helper methods ----------------------------------------------------
/**
* Get an iterable set of resources filtered by the given request and
* predicate objects.
*
* @param type type of resources
* @param request the request
* @param predicate the predicate object which filters which resources are returned
*
* @return a page response representing the requested page of resources
*
* @throws UnsupportedPropertyException thrown if the request or predicate contain
* unsupported property ids
* @throws SystemException an internal exception occurred
* @throws NoSuchResourceException no matching resource(s) found
* @throws NoSuchParentResourceException a specified parent resource doesn't exist
*/
protected Iterable<Resource> getResourceIterable(Resource.Type type, Request request, Predicate predicate)
throws UnsupportedPropertyException,
SystemException,
NoSuchParentResourceException,
NoSuchResourceException {
PageResponse resources = getResources(type, request, predicate, null);
return resources.getIterable();
}
/**
* Get a page of resources filtered by the given request, predicate objects and
* page request.
*
* @param type type of resources
* @param request the request
* @param predicate the predicate object which filters which resources are returned
* @param pageRequest the page request for a paginated response
*
* @return a page response representing the requested page of resources
*
* @throws UnsupportedPropertyException thrown if the request or predicate contain
* unsupported property ids
* @throws SystemException an internal exception occurred
* @throws NoSuchResourceException no matching resource(s) found
* @throws NoSuchParentResourceException a specified parent resource doesn't exist
*/
protected PageResponse getResources(Resource.Type type, Request request, Predicate predicate, PageRequest pageRequest)
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException {
Set<Resource> providerResources = getResources(type, request, predicate);
populateResources(type, providerResources, request, predicate);
return getPage(type, providerResources, request, predicate, pageRequest);
}
/**
* Check to make sure that all the property ids specified in the given request and
* predicate are supported by the resource provider or property providers for the
* given type.
*
* @param type the resource type
* @param request the request
* @param predicate the predicate
*
* @return true if all of the properties specified in the request and predicate are supported by
* the resource provider for the given type; false if any of the properties specified in
* the request and predicate are not supported by the resource provider but are supported
* by a property provider for the given type.
*
* @throws UnsupportedPropertyException thrown if any of the properties specified in the request
* and predicate are not supported by either the resource
* provider or a property provider for the given type
*/
private boolean checkProperties(Resource.Type type, Request request, Predicate predicate)
throws UnsupportedPropertyException {
Set<String> requestPropertyIds = request == null ? new HashSet<String>() :
PropertyHelper.getAssociatedPropertyIds(request);
if (predicate != null) {
requestPropertyIds.addAll(PredicateHelper.getPropertyIds(predicate));
}
if (requestPropertyIds.size() > 0) {
ResourceProvider provider = ensureResourceProvider(type);
requestPropertyIds = provider.checkPropertyIds(requestPropertyIds);
if (requestPropertyIds.size() > 0) {
List<PropertyProvider> propertyProviders = ensurePropertyProviders(type);
for (PropertyProvider propertyProvider : propertyProviders) {
requestPropertyIds = propertyProvider.checkPropertyIds(requestPropertyIds);
if (requestPropertyIds.size() == 0) {
return false;
}
}
throw new UnsupportedPropertyException(type, requestPropertyIds);
}
}
return true;
}
/**
* Check to see if any of the property ids specified in the given request and
* predicate are handled by an associated property provider. if so, then use
* the given predicate to obtain a new predicate that can be completely
* processed by an update or delete operation on a resource provider for
* the given resource type. This means that the new predicate should only
* reference the key property ids for this type.
*
* @param type the resource type
* @param predicate the predicate
*
* @return the given predicate if a new one is not required; a new predicate if required
*
* @throws UnsupportedPropertyException thrown if any of the properties specified in the request
* and predicate are not supported by either the resource
* provider or a property provider for the given type
*
* @throws SystemException thrown for internal exceptions
* @throws NoSuchResourceException if the resource that is requested doesn't exist
* @throws NoSuchParentResourceException if a parent resource of the requested resource doesn't exist
*/
private Predicate resolvePredicate(Resource.Type type, Predicate predicate)
throws UnsupportedPropertyException,
SystemException,
NoSuchResourceException,
NoSuchParentResourceException{
ResourceProvider provider = ensureResourceProvider(type);
Set<String> keyPropertyIds = new HashSet<String>(provider.getKeyPropertyIds().values());
Request readRequest = PropertyHelper.getReadRequest(keyPropertyIds);
Iterable<Resource> resources = getResourceIterable(type, readRequest, predicate);
PredicateBuilder pb = new PredicateBuilder();
PredicateBuilder.PredicateBuilderWithPredicate pbWithPredicate = null;
for (Resource resource : resources) {
if (pbWithPredicate != null) {
pb = pbWithPredicate.or();
}
pb = pb.begin();
pbWithPredicate = null;
for (String keyPropertyId : keyPropertyIds) {
if (pbWithPredicate != null) {
pb = pbWithPredicate.and();
}
pbWithPredicate =
pb.property(keyPropertyId).equals((Comparable) resource.getPropertyValue(keyPropertyId));
}
if (pbWithPredicate != null) {
pbWithPredicate = pbWithPredicate.end();
}
}
return pbWithPredicate == null ? null : pbWithPredicate.toPredicate();
}
/**
* Indicates whether or not the given property provider can service the given request.
*
* @param provider the property provider
* @param request the request
* @param predicate the predicate
*
* @return true if the given provider can service the request
*/
private boolean providesRequestProperties(PropertyProvider provider, Request request, Predicate predicate) {
Set<String> requestPropertyIds = new HashSet<String>(request.getPropertyIds());
if (requestPropertyIds.size() == 0) {
return true;
}
requestPropertyIds.addAll(PredicateHelper.getPropertyIds(predicate));
int size = requestPropertyIds.size();
return size > provider.checkPropertyIds(requestPropertyIds).size();
}
/**
* Get the resource provider for the given type, creating it if required.
*
* @param type the resource type
*
* @return the resource provider
*/
private ResourceProvider ensureResourceProvider(Resource.Type type) {
synchronized (resourceProviders) {
if (!resourceProviders.containsKey(type)) {
resourceProviders.put(type, providerModule.getResourceProvider(type));
}
}
return resourceProviders.get(type);
}
/**
* Get the list of property providers for the given type.
*
* @param type the resource type
*
* @return the list of property providers
*/
private List<PropertyProvider> ensurePropertyProviders(Resource.Type type) {
synchronized (propertyProviders) {
if (!propertyProviders.containsKey(type)) {
propertyProviders.put(type, providerModule.getPropertyProviders(type));
}
}
return propertyProviders.get(type);
}
/**
* Get one page of resources from the given set of resources starting at the given offset.
*
* @param pageSize the page size
* @param offset the offset
* @param resources the set of resources
* @param request the request
* @param predicate the predicate
*
* @return a page response containing a page of resources
*/
private PageResponse getPageFromOffset(int pageSize, int offset,
NavigableSet<Resource> resources,
Request request, Predicate predicate,
ResourcePredicateEvaluator evaluator) {
int currentOffset = 0;
Resource previous = null;
Set<Resource> pageResources = new LinkedHashSet<Resource>();
Iterator<Resource> iterator = resources.iterator();
// skip till offset
while (currentOffset < offset && iterator.hasNext()) {
previous = iterator.next();
++currentOffset;
}
// get a page worth of resources
for (int i = 0; i < pageSize && iterator.hasNext(); ++i) {
pageResources.add(iterator.next());
}
return new PageResponseImpl(new ResourceIterable(pageResources,
request, predicate, evaluator),
currentOffset,
previous,
iterator.hasNext() ? iterator.next() : null);
}
/**
* Get one page of resources from the given set of resources ending at the given offset.
*
* @param pageSize the page size
* @param offset the offset; -1 indicates the end of the resource set
* @param resources the set of resources
* @param request the request
* @param predicate the predicate
*
* @return a page response containing a page of resources
*/
private PageResponse getPageToOffset(int pageSize, int offset,
NavigableSet<Resource> resources,
Request request, Predicate predicate,
ResourcePredicateEvaluator evaluator) {
int currentOffset = resources.size() - 1;
Resource next = null;
List<Resource> pageResources = new LinkedList<Resource>();
Iterator<Resource> iterator = resources.descendingIterator();
if (offset != -1) {
// skip till offset
while (currentOffset > offset && iterator.hasNext()) {
next = iterator.next();
--currentOffset;
}
}
// get a page worth of resources
for (int i = 0; i < pageSize && iterator.hasNext(); ++i) {
pageResources.add(0, iterator.next());
--currentOffset;
}
return new PageResponseImpl(new ResourceIterable(new
LinkedHashSet<Resource>(pageResources), request, predicate, evaluator),
currentOffset + 1,
iterator.hasNext() ? iterator.next() : null,
next);
}
/**
* Get the associated resource comparator.
*
* @return the resource comparator
*/
protected Comparator<Resource> getComparator() {
return comparator;
}
// ----- ResourceIterable inner class --------------------------------------
private static class ResourceIterable implements Iterable<Resource> {
/**
* The resources to iterate over.
*/
private final Set<Resource> resources;
/**
* The associated request.
*/
private final Request request;
/**
* The predicate used to filter the set.
*/
private final Predicate predicate;
/**
* The predicate evaluator.
*/
private final ResourcePredicateEvaluator evaluator;
// ----- Constructors ----------------------------------------------------
/**
* Create a ResourceIterable.
*
* @param resources the set of resources to iterate over
* @param request the request
* @param predicate the predicate used to filter the set of resources
* @param evaluator the evaluator used to evaluate with the given predicate
*/
private ResourceIterable(Set<Resource> resources, Request request, Predicate predicate,
ResourcePredicateEvaluator evaluator) {
this.resources = resources;
this.request = request;
this.predicate = predicate;
this.evaluator = evaluator;
}
// ----- Iterable --------------------------------------------------------
@Override
public Iterator<Resource> iterator() {
return new ResourceIterator(resources, request, predicate, evaluator);
}
}
// ----- ResourceIterator inner class --------------------------------------
private static class ResourceIterator implements Iterator<Resource> {
/**
* The underlying iterator.
*/
private final Iterator<Resource> iterator;
/**
* The associated request.
*/
private final Request request;
/**
* The predicate used to filter the resource being iterated over.
*/
private final Predicate predicate;
/**
* The next resource.
*/
private Resource nextResource;
/**
* The predicate evaluator.
*/
private final ResourcePredicateEvaluator evaluator;
// ----- Constructors ----------------------------------------------------
/**
* Create a new ResourceIterator.
*
* @param resources the set of resources to iterate over
* @param request the request
* @param predicate the predicate used to filter the set of resources
* @param evaluator the evaluator used to evaluate with the given predicate
*/
private ResourceIterator(Set<Resource> resources, Request request, Predicate predicate,
ResourcePredicateEvaluator evaluator) {
this.iterator = resources.iterator();
this.request = request;
this.predicate = predicate;
this.evaluator = evaluator;
this.nextResource = getNextResource();
}
// ----- Iterator --------------------------------------------------------
@Override
public boolean hasNext() {
return nextResource != null;
}
@Override
public Resource next() {
if (nextResource == null) {
throw new NoSuchElementException("Iterator has no more elements.");
}
Resource currentResource = nextResource;
this.nextResource = getNextResource();
return currentResource;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported.");
}
// ----- helper methods --------------------------------------------------
/**
* Get the next resource.
*
* @return the next resource.
*/
private Resource getNextResource() {
while (iterator.hasNext()) {
Resource next = iterator.next();
if (predicate == null || evaluator.evaluate(predicate, next)) {
// TODO : copy the resource and only include the requested properties.
return next;
}
}
return null;
}
}
// ----- ResourceComparator inner class ------------------------------------
protected class ResourceComparator implements Comparator<Resource> {
@Override
public int compare(Resource resource1, Resource resource2) {
Resource.Type resourceType = resource1.getType();
// compare based on resource type
int compVal = resourceType.compareTo(resource2.getType());
if (compVal == 0) {
Schema schema = getSchema(resourceType);
// compare based on resource key properties
for (Resource.Type type : Resource.Type.values()) {
String keyPropertyId = schema.getKeyPropertyId(type);
if (keyPropertyId != null) {
compVal = compareValues(resource1.getPropertyValue(keyPropertyId),
resource2.getPropertyValue(keyPropertyId));
if (compVal != 0 ) {
return compVal;
}
}
}
}
// compare based on the resource strings
return resource1.toString().compareTo(resource2.toString());
}
// compare two values and account for null
private int compareValues(Object val1, Object val2) {
if (val1 == null || val2 == null) {
return val1 == null && val2 == null ? 0 : val1 == null ? -1 : 1;
}
if (val1 instanceof Comparable) {
try {
return ((Comparable)val1).compareTo(val2);
} catch (ClassCastException e) {
return 0;
}
}
return 0;
}
}
}