All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vmware.photon.controller.model.resources.ResourceUtils Maven / Gradle / Ivy

/*
 * Copyright (c) 2015-2016 VMware, Inc. All Rights Reserved.
 *
 * Licensed 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 com.vmware.photon.controller.model.resources;

import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.ReflectionUtils;
import com.vmware.xenon.common.ServiceDocument;
import com.vmware.xenon.common.ServiceDocumentDescription;
import com.vmware.xenon.common.ServiceDocumentDescription.PropertyDescription;
import com.vmware.xenon.common.ServiceDocumentDescription.PropertyUsageOption;
import com.vmware.xenon.common.StatefulService;
import com.vmware.xenon.common.Utils;

public class ResourceUtils {

    /**
     * Optional link fields in resources cannot be cleared with a regular PATCH request because the
     * automatic merge just ignores {@code null} fields from the PATCH body, for optimization
     * purposes.
     *
     * This constant can be used instead of a {@code null} value. It is applicable only for
     * {@link PropertyUsageOption.LINK} fields that are marked with
     * {@link PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL} flag in the resource state document.
     */
    public static final String NULL_LINK_VALUE = "__noLink";

    /**
     * This method handles merging of state for patch requests. It first checks to see if the
     * patch body is for updating collections. If not it invokes the mergeWithState() method.
     * Finally, users can specify a custom callback method to perform service specific merge
     * operations.
     *
     * 

If no changes are made to the current state, a response code {@code NOT_MODIFIED} is * returned with no body. If changes are made, the response body contains the full updated * state. * * @param op Input PATCH operation * @param currentState The current state of the service * @param description The service description * @param stateClass Service state class * @param customPatchHandler custom callback handler */ public static void handlePatch(Operation op, T currentState, ServiceDocumentDescription description, Class stateClass, Function customPatchHandler) { try { boolean hasStateChanged; // apply standard patch merging EnumSet mergeResult = Utils.mergeWithStateAdvanced(description, currentState, stateClass, op); hasStateChanged = mergeResult.contains(Utils.MergeResult.STATE_CHANGED); if (!mergeResult.contains(Utils.MergeResult.SPECIAL_MERGE)) { T patchBody = op.getBody(stateClass); // apply ResourceState-specific merging hasStateChanged |= ResourceUtils.mergeResourceStateWithPatch(currentState, patchBody); // handle NULL_LINK_VALUE links hasStateChanged |= nullifyLinkFields(description, currentState, patchBody); } // apply custom patch handler, if any if (customPatchHandler != null) { hasStateChanged |= customPatchHandler.apply(op); } if (!hasStateChanged) { op.setStatusCode(Operation.STATUS_CODE_NOT_MODIFIED); } else { op.setBody(currentState); } op.complete(); } catch (NoSuchFieldException | IllegalAccessException e) { op.fail(e); } } /** * Updates the state of the service based on the input patch. * * @param source currentState of the service * @param patch patch state * @return whether the state has changed or not */ private static boolean mergeResourceStateWithPatch(ResourceState source, ResourceState patch) { boolean isChanged = false; // tenantLinks requires special handling so that although it is a list, duplicate items // are not allowed (i.e. it should behave as a set) if (patch.tenantLinks != null && !patch.tenantLinks.isEmpty()) { if (source.tenantLinks == null || source.tenantLinks.isEmpty()) { source.tenantLinks = patch.tenantLinks; isChanged = true; } else { for (String e : patch.tenantLinks) { if (!source.tenantLinks.contains(e)) { source.tenantLinks.add(e); isChanged = true; } } } } return isChanged; } /** * Nullifies link fields if the patch body contains NULL_LINK_VALUE links. */ private static boolean nullifyLinkFields( ServiceDocumentDescription desc, T currentState, T patchBody) { boolean modified = false; for (PropertyDescription prop : desc.propertyDescriptions.values()) { if (prop.usageOptions != null && prop.usageOptions.contains(PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL) && prop.usageOptions.contains(PropertyUsageOption.LINK)) { Object patchValue = ReflectionUtils.getPropertyValue(prop, patchBody); if (NULL_LINK_VALUE.equals(patchValue)) { Object currentValue = ReflectionUtils.getPropertyValue(prop, currentState); modified |= currentValue != null; ReflectionUtils.setPropertyValue(prop, currentState, null); } } } return modified; } public static void handleDelete(Operation op, StatefulService service) { service.logInfo("Deleting document %s, Operation ID: %d, Referrer: %s", op.getUri().getPath(), op.getId(), op.getRefererAsString()); ServiceDocument currentState = service.getState(op); // If delete request specifies the document expiration time then set that, otherwise // by default set the expiration to one month later. if (op.hasBody()) { ServiceDocument opState = op.getBody(ServiceDocument.class); if (opState.documentExpirationTimeMicros > 0) { currentState.documentExpirationTimeMicros = opState.documentExpirationTimeMicros; } } else { currentState.documentExpirationTimeMicros = Utils.getNowMicrosUtc() + TimeUnit.DAYS.toMicros(31); } op.complete(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy