/** * 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.api.handlers; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.ambari.server.api.resources.ResourceInstance; import org.apache.ambari.server.api.resources.ResourceInstanceFactory; import org.apache.ambari.server.api.resources.ResourceInstanceFactoryImpl; import org.apache.ambari.server.api.services.NamedPropertySet; import org.apache.ambari.server.api.services.Request; import org.apache.ambari.server.api.services.RequestBody; import org.apache.ambari.server.api.services.Result; import org.apache.ambari.server.api.services.ResultImpl; import org.apache.ambari.server.api.services.ResultMetadata; import org.apache.ambari.server.api.services.ResultStatus; import org.apache.ambari.server.api.util.TreeNode; import org.apache.ambari.server.controller.spi.ClusterController; import org.apache.ambari.server.controller.spi.NoSuchParentResourceException; import org.apache.ambari.server.controller.spi.RequestStatus; import org.apache.ambari.server.controller.spi.RequestStatusMetaData; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException; import org.apache.ambari.server.controller.spi.SystemException; import org.apache.ambari.server.controller.spi.UnsupportedPropertyException; import org.apache.ambari.server.security.authorization.AuthorizationException; /** * Handler for creates that are applied to the results of a query. */ public class QueryCreateHandler extends BaseManagementHandler { private RequestHandler m_readHandler = new ReadHandler(); @Override public Result handleRequest(Request request) { Result queryResult = getReadHandler().handleRequest(request); if (queryResult.getStatus().isErrorState() || queryResult.getResultTree().getChildren().isEmpty()) { // if query result has error state or contains no resources just return it. // currently returns 200 for case where query returns no rows return queryResult; } Map<Resource.Type, Set<Map<String, Object>>> mapProperties; try { mapProperties = buildCreateSet(request, queryResult); } catch (IllegalArgumentException e) { return createInvalidRequestResult(e.getMessage()); } if (mapProperties.size() != 1) { return createInvalidRequestResult(mapProperties.size() == 0 ? "A minimum of one sub-resource must be specified for creation." : "Multiple sub-resource types may not be created in the same request."); } // only get first element because we currently only support creation of a single sub-resource type final Map.Entry<Resource.Type, Set<Map<String, Object>>> entry = mapProperties.entrySet().iterator().next(); ResourceInstance createResource = getResourceFactory().createResource( entry.getKey(), request.getResource().getKeyValueMap()); RequestBody requestBody = new RequestBody(); requestBody.setBody(request.getBody().getBody()); for (Map<String, Object> map : entry.getValue()) { requestBody.addPropertySet(new NamedPropertySet("", map)); } return persist(createResource, requestBody ); } /** * Build the property set for all sub-resource to be created. * This includes determining the sub-resource type and creating a property set for each matching parent. * * @param request the current request * @param queryResult the result of the query for matching parents * * @return a map of sub-resource types to be created and their associated properties * * @throws IllegalArgumentException if no sub-resource type was specified or it is not a valid * sub-resource of the parent. */ private Map<Resource.Type, Set<Map<String, Object>>> buildCreateSet(Request request, Result queryResult) throws IllegalArgumentException { Set<NamedPropertySet> setRequestProps = request.getBody().getNamedPropertySets(); HashMap<Resource.Type, Set<Map<String, Object>>> mapProps = new HashMap<>(); ResourceInstance resource = request.getResource(); Resource.Type type = resource.getResourceDefinition().getType(); ClusterController controller = getClusterController(); String resourceKeyProperty = controller.getSchema(type).getKeyPropertyId(type); TreeNode<Resource> tree = queryResult.getResultTree(); Collection<TreeNode<Resource>> treeChildren = tree.getChildren(); for (TreeNode<Resource> node : treeChildren) { Resource r = node.getObject(); Object keyVal = r.getPropertyValue(resourceKeyProperty); for (NamedPropertySet namedProps : setRequestProps) { for (Map.Entry<String, Object> entry : namedProps.getProperties().entrySet()) { Set<Map<String, Object>> set = (Set<Map<String, Object>>) entry.getValue(); for (Map<String, Object> map : set) { Map<String, Object> mapResourceProps = new HashMap<>(map); Resource.Type createType = getCreateType(resource, entry.getKey()); mapResourceProps.put(controller.getSchema(createType). getKeyPropertyId(resource.getResourceDefinition().getType()), keyVal); Set<Map<String, Object>> setCreateProps = mapProps.get(createType); if (setCreateProps == null) { setCreateProps = new HashSet<>(); mapProps.put(createType, setCreateProps); } setCreateProps.add(mapResourceProps); } } } } return mapProps; } /** * Determine the sub-resource type(s) to be created. * * @param resource the requests resource instance * @param subResourceName the name of the sub-resource to be created * @return the resource type * * @throws IllegalArgumentException if the specified sub-resource name is empty or it is not a valid * sub-resource of the parent. */ private Resource.Type getCreateType(ResourceInstance resource, String subResourceName) throws IllegalArgumentException{ if (subResourceName == null || subResourceName.equals("")) { throw new IllegalArgumentException("A sub-resource name must be supplied."); } ResourceInstance res = resource.getSubResources().get(subResourceName); if (res == null) { throw new IllegalArgumentException("The specified sub-resource name is not valid: '" + subResourceName + "'."); } return res.getResourceDefinition().getType(); } /** * Convenience method to create a result for invalid requests. * * @param msg message indicating why the request is invalid * * @return a request with a 400 status and msg set */ private Result createInvalidRequestResult(String msg) { return new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, "Invalid Request: " + msg)); } @Override protected Result persist(ResourceInstance resource, RequestBody body) { Result result; try { RequestStatus status = getPersistenceManager().create(resource, body); result = createResult(status); if (result.isSynchronous()) { if (resource.getResourceDefinition().isCreatable()) { result.setResultStatus(new ResultStatus(ResultStatus.STATUS.CREATED)); } else { result.setResultStatus(new ResultStatus(ResultStatus.STATUS.OK)); } } else { result.setResultStatus(new ResultStatus(ResultStatus.STATUS.ACCEPTED)); } } catch (AuthorizationException e) { result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.FORBIDDEN, e.getMessage())); } catch (UnsupportedPropertyException e) { result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, e)); } catch (ResourceAlreadyExistsException e) { result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.CONFLICT, e)); } catch (NoSuchParentResourceException e) { result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.NOT_FOUND, e)); } catch (SystemException e) { result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.SERVER_ERROR, e)); } return result; } @Override protected ResultMetadata convert(RequestStatusMetaData requestStatusMetaData) { if (requestStatusMetaData == null) { return null; } throw new UnsupportedOperationException(); } /** * Get the resource factory instance. * @return a factory for creating resource instances */ protected ResourceInstanceFactory getResourceFactory() { //todo: inject return new ResourceInstanceFactoryImpl(); } /** * Read handler instance. Used for obtaining matching parents which match the query. * * @return read handler instance */ protected RequestHandler getReadHandler() { return m_readHandler; } }