org.apache.helix.model.IdealState.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.helix.model.IdealState.java

Source

package org.apache.helix.model;

/*
 * 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.
 */

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.helix.HelixConstants;
import org.apache.helix.HelixDefinedState;
import org.apache.helix.HelixProperty;
import org.apache.helix.ZNRecord;
import org.apache.helix.api.State;
import org.apache.helix.api.config.NamespacedConfig;
import org.apache.helix.api.config.UserConfig;
import org.apache.helix.api.id.ParticipantId;
import org.apache.helix.api.id.PartitionId;
import org.apache.helix.api.id.ResourceId;
import org.apache.helix.api.id.StateModelDefId;
import org.apache.helix.api.id.StateModelFactoryId;
import org.apache.helix.controller.rebalancer.CustomRebalancer;
import org.apache.helix.controller.rebalancer.FullAutoRebalancer;
import org.apache.helix.controller.rebalancer.HelixRebalancer;
import org.apache.helix.controller.rebalancer.RebalancerRef;
import org.apache.helix.controller.rebalancer.SemiAutoRebalancer;
import org.apache.helix.task.FixedTargetTaskRebalancer;
import org.apache.helix.task.GenericTaskRebalancer;
import org.apache.log4j.Logger;

import com.google.common.base.Enums;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;

/**
 * The ideal states of all partitions in a resource
 */
public class IdealState extends HelixProperty {
    private static final Logger LOG = Logger.getLogger(IdealState.class);

    /**
     * Properties that are persisted and are queryable for an ideal state
     */
    public enum IdealStateProperty {
        NUM_PARTITIONS, STATE_MODEL_DEF_REF, STATE_MODEL_FACTORY_NAME, REPLICAS, @Deprecated
        IDEAL_STATE_MODE, REBALANCE_MODE, REBALANCE_TIMER_PERIOD, MAX_PARTITIONS_PER_INSTANCE, INSTANCE_GROUP_TAG, REBALANCER_CLASS_NAME, REBALANCER_CONFIG_NAME, HELIX_ENABLED
    }

    public static final String QUERY_LIST = "PREFERENCE_LIST_QUERYS";

    /**
     * Deprecated.
     * @see {@link RebalanceMode}
     */
    @Deprecated
    public enum IdealStateModeProperty {
        AUTO, CUSTOMIZED, AUTO_REBALANCE
    }

    /**
     * The mode used for rebalance. FULL_AUTO does both node location calculation and state
     * assignment, SEMI_AUTO only does the latter, and CUSTOMIZED does neither. USER_DEFINED
     * uses a Rebalancer implementation plugged in by the user.
     */
    public enum RebalanceMode {
        FULL_AUTO, SEMI_AUTO, CUSTOMIZED, USER_DEFINED, TASK, NONE
    }

    private static final Logger logger = Logger.getLogger(IdealState.class.getName());

    /**
     * Instantiate an ideal state for a resource
     * @param resourceName the name of the resource
     */
    public IdealState(String resourceName) {
        super(resourceName);
    }

    /**
     * Instantiate an ideal state for a resource
     * @param resourceId the id of the resource
     */
    public IdealState(ResourceId resourceId) {
        super(resourceId.stringify());
    }

    /**
     * Instantiate an ideal state from a record
     * @param record ZNRecord corresponding to an ideal state
     */
    public IdealState(ZNRecord record) {
        super(record);
    }

    /**
     * Get the associated resource
     * @return the name of the resource
     */
    public String getResourceName() {
        return _record.getId();
    }

    /**
     * Get the associated resource
     * @return the id of the resource
     */
    public ResourceId getResourceId() {
        return ResourceId.from(getResourceName());
    }

    /**
     * Get the rebalance mode of the ideal state
     * @param mode {@link IdealStateModeProperty}
     */
    @Deprecated
    public void setIdealStateMode(String mode) {
        _record.setSimpleField(IdealStateProperty.IDEAL_STATE_MODE.toString(), mode);
        RebalanceMode rebalanceMode = normalizeRebalanceMode(IdealStateModeProperty.valueOf(mode));
        _record.setEnumField(IdealStateProperty.REBALANCE_MODE.toString(), rebalanceMode);
    }

    /**
     * Get the rebalance mode of the resource
     * @param rebalancerType
     */
    public void setRebalanceMode(RebalanceMode rebalancerType) {
        _record.setEnumField(IdealStateProperty.REBALANCE_MODE.toString(), rebalancerType);
        IdealStateModeProperty idealStateMode = denormalizeRebalanceMode(rebalancerType);
        _record.setEnumField(IdealStateProperty.IDEAL_STATE_MODE.toString(), idealStateMode);
    }

    /**
     * Get the maximum number of partitions an instance can serve
     * @return the partition capacity of an instance for this resource, or Integer.MAX_VALUE
     */
    public int getMaxPartitionsPerInstance() {
        return _record.getIntField(IdealStateProperty.MAX_PARTITIONS_PER_INSTANCE.toString(), Integer.MAX_VALUE);
    }

    /**
     * Define a custom rebalancer that implements {@link HelixRebalancer}
     * @param rebalancerClassName the name of the custom rebalancing class
     */
    public void setRebalancerClassName(String rebalancerClassName) {
        _record.setSimpleField(IdealStateProperty.REBALANCER_CLASS_NAME.toString(), rebalancerClassName);
    }

    /**
     * Get the name of the user-defined rebalancer associated with this resource
     * @return the rebalancer class name, or null if none is being used
     */
    public String getRebalancerClassName() {
        return _record.getSimpleField(IdealStateProperty.REBALANCER_CLASS_NAME.toString());
    }

    /**
     * Set a reference to the user-defined rebalancer associated with this resource(if any)
     * @param rebalancerRef a reference to a user-defined rebalancer
     */
    public void setRebalancerRef(RebalancerRef rebalancerRef) {
        if (rebalancerRef != null) {
            setRebalancerClassName(rebalancerRef.toString());
        } else {
            setRebalancerClassName(null);
        }
    }

    /**
     * Get a reference to the user-defined rebalancer associated with this resource(if any)
     * @return RebalancerRef
     */
    public RebalancerRef getRebalancerRef() {
        RebalancerRef ref = null;
        String className = getRebalancerClassName();
        if (className != null) {
            ref = RebalancerRef.from(getRebalancerClassName());
        } else {
            switch (getRebalanceMode()) {
            case FULL_AUTO:
                ref = RebalancerRef.from(FullAutoRebalancer.class);
                break;
            case SEMI_AUTO:
                ref = RebalancerRef.from(SemiAutoRebalancer.class);
                break;
            case CUSTOMIZED:
                ref = RebalancerRef.from(CustomRebalancer.class);
                break;
            default:
                break;
            }
        }
        return ref;
    }

    /**
     * Set the maximum number of partitions of this resource that an instance can serve
     * @param max the maximum number of partitions supported
     */
    public void setMaxPartitionsPerInstance(int max) {
        _record.setIntField(IdealStateProperty.MAX_PARTITIONS_PER_INSTANCE.toString(), max);
    }

    /**
     * Get the rebalancing mode on this resource
     * @return {@link IdealStateModeProperty}
     */
    @Deprecated
    public IdealStateModeProperty getIdealStateMode() {
        return _record.getEnumField(IdealStateProperty.IDEAL_STATE_MODE.toString(), IdealStateModeProperty.class,
                IdealStateModeProperty.AUTO);
    }

    /**
     * Get the rebalancing mode on this resource
     * @return {@link RebalanceMode}
     */
    public RebalanceMode getRebalanceMode() {
        RebalanceMode property = _record.getEnumField(IdealStateProperty.REBALANCE_MODE.toString(),
                RebalanceMode.class, RebalanceMode.NONE);
        if (property == RebalanceMode.NONE) {
            property = normalizeRebalanceMode(getIdealStateMode());
            setRebalanceMode(property);
        }
        return property;
    }

    /**
     * Set the preferred instance placement and state for a partition replica
     * @param partitionName the replica to set
     * @param instanceName the assigned instance
     * @param state the replica state in this instance
     */
    public void setPartitionState(String partitionName, String instanceName, String state) {
        Map<String, String> mapField = _record.getMapField(partitionName);
        if (mapField == null) {
            _record.setMapField(partitionName, new TreeMap<String, String>());
        }
        _record.getMapField(partitionName).put(instanceName, state);
    }

    /**
     * Set the preferred participant placement and state for a partition replica
     * @param partitionId the replica to set
     * @param participantId the assigned participant
     * @param state the replica state in this instance
     */
    public void setPartitionState(PartitionId partitionId, ParticipantId participantId, State state) {
        Map<String, String> mapField = _record.getMapField(partitionId.stringify());
        if (mapField == null) {
            _record.setMapField(partitionId.stringify(), new TreeMap<String, String>());
        }
        _record.getMapField(partitionId.stringify()).put(participantId.stringify(), state.toString());
    }

    /**
     * Get all of the partitions
     * @return a set of partition names
     */
    public Set<String> getPartitionSet() {
        switch (getRebalanceMode()) {
        case SEMI_AUTO:
        case FULL_AUTO:
            return _record.getListFields().keySet();
        case CUSTOMIZED:
        case USER_DEFINED:
        case TASK:
            return _record.getMapFields().keySet();
        default:
            logger.error("Invalid ideal state mode:" + getResourceName());
            return Collections.emptySet();
        }
    }

    /**
     * Get all of the partitions
     * @return a set of partitions
     */
    public Set<PartitionId> getPartitionIdSet() {
        Set<PartitionId> partitionSet = Sets.newHashSet();
        for (String partitionName : getPartitionSet()) {
            partitionSet.add(PartitionId.from(partitionName));
        }
        return partitionSet;
    }

    /**
     * Get the current mapping of a partition
     * @param partitionName the name of the partition
     * @return the instances where the replicas live and the state of each
     */
    public Map<String, String> getInstanceStateMap(String partitionName) {
        return _record.getMapField(partitionName);
    }

    /**
     * Set the current mapping of a partition
     * @param partitionName the partition to set
     * @param instanceStateMap (participant name, state) pairs
     */
    public void setInstanceStateMap(String partitionName, Map<String, String> instanceStateMap) {
        _record.setMapField(partitionName, instanceStateMap);
    }

    /**
     * Set the current mapping of a partition
     * @param partitionId the partition to set
     * @param participantStateMap (participant id, state) pairs
     */
    public void setParticipantStateMap(PartitionId partitionId, Map<ParticipantId, State> participantStateMap) {
        Map<String, String> rawMap = new HashMap<String, String>();
        for (ParticipantId participantId : participantStateMap.keySet()) {
            rawMap.put(participantId.stringify(), participantStateMap.get(participantId).toString());
        }
        _record.setMapField(partitionId.stringify(), rawMap);
    }

    /**
     * Get the current mapping of a partition
     * @param partitionId the name of the partition
     * @return the instances where the replicas live and the state of each
     */
    public Map<ParticipantId, State> getParticipantStateMap(PartitionId partitionId) {
        Map<String, String> instanceStateMap = getInstanceStateMap(partitionId.stringify());
        Map<ParticipantId, State> participantStateMap = Maps.newHashMap();
        if (instanceStateMap != null) {
            for (String participantId : instanceStateMap.keySet()) {
                participantStateMap.put(ParticipantId.from(participantId),
                        State.from(instanceStateMap.get(participantId)));
            }
            return participantStateMap;
        }
        return null;
    }

    /**
     * Get the instances who host replicas of a partition
     * @param partitionName the partition to look up
     * @return set of instance names
     */
    public Set<String> getInstanceSet(String partitionName) {
        RebalanceMode rebalanceMode = getRebalanceMode();
        if (rebalanceMode == RebalanceMode.SEMI_AUTO || rebalanceMode == RebalanceMode.FULL_AUTO) {
            // get instances from list fields
            List<String> prefList = _record.getListField(partitionName);
            if (prefList != null) {
                return new TreeSet<String>(prefList);
            } else {
                logger.warn(partitionName + " does NOT exist");
                return Collections.emptySet();
            }
        } else if (rebalanceMode == RebalanceMode.CUSTOMIZED || rebalanceMode == RebalanceMode.USER_DEFINED
                || rebalanceMode == RebalanceMode.TASK) {
            // get instances from map fields
            Map<String, String> stateMap = _record.getMapField(partitionName);
            if (stateMap != null) {
                return new TreeSet<String>(stateMap.keySet());
            } else {
                logger.warn(partitionName + " does NOT exist");
                return Collections.emptySet();
            }
        } else {
            logger.error("Invalid ideal state mode: " + getResourceName());
            return Collections.emptySet();
        }
    }

    /**
     * Get the participants who host replicas of a partition
     * @param partitionId the partition to look up
     * @return set of participant ids
     */
    public Set<ParticipantId> getParticipantSet(PartitionId partitionId) {
        Set<ParticipantId> participantSet = Sets.newHashSet();
        for (String participantName : getInstanceSet(partitionId.stringify())) {
            participantSet.add(ParticipantId.from(participantName));
        }
        return participantSet;
    }

    /**
     * Set the preference list of a partition
     * @param partitionName the name of the partition to set
     * @param preferenceList a list of participants that can serve replicas of the partition
     */
    public void setPreferenceList(String partitionName, List<String> preferenceList) {
        _record.setListField(partitionName, preferenceList);
    }

    /**
     * Set the preference list of a partition
     * @param partitionId the id of the partition to set
     * @param preferenceList a list of participants that can serve replicas of the partition
     */
    public void setPreferenceList(PartitionId partitionId, List<ParticipantId> preferenceList) {
        if (preferenceList == null) {
            preferenceList = Collections.emptyList();
        }
        List<String> rawPreferenceList = new ArrayList<String>();
        for (ParticipantId participantId : preferenceList) {
            rawPreferenceList.add(participantId.stringify());
        }
        _record.setListField(partitionId.stringify(), rawPreferenceList);
    }

    /**
     * Get the preference list of a partition
     * @param partitionName the name of the partition
     * @return a list of instances that can serve replicas of the partition
     */
    public List<String> getPreferenceList(String partitionName) {
        List<String> instanceStateList = _record.getListField(partitionName);

        if (instanceStateList != null) {
            return instanceStateList;
        }
        logger.warn("Resource key:" + partitionName + " does not have a pre-computed preference list.");
        return null;
    }

    /**
     * Get the preference list of a partition
     * @param partitionId the partition id
     * @return an ordered list of participants that can serve replicas of the partition
     */
    public List<ParticipantId> getPreferenceList(PartitionId partitionId) {
        List<ParticipantId> preferenceList = Lists.newArrayList();
        List<String> preferenceStringList = getPreferenceList(partitionId.stringify());
        if (preferenceStringList != null) {
            for (String participantName : preferenceStringList) {
                preferenceList.add(ParticipantId.from(participantName));
            }
            return preferenceList;
        }
        return null;
    }

    /**
     * Get the state model associated with this resource
     * @return an identifier of the state model
     */
    public String getStateModelDefRef() {
        return _record.getSimpleField(IdealStateProperty.STATE_MODEL_DEF_REF.toString());
    }

    /**
     * Get the state model associated with this resource
     * @return an identifier of the state model
     */
    public StateModelDefId getStateModelDefId() {
        return StateModelDefId.from(getStateModelDefRef());
    }

    /**
     * Set the state model associated with this resource
     * @param stateModel state model identifier
     */
    public void setStateModelDefRef(String stateModel) {
        _record.setSimpleField(IdealStateProperty.STATE_MODEL_DEF_REF.toString(), stateModel);
    }

    /**
     * Set the state model associated with this resource
     * @param stateModel state model identifier
     */
    public void setStateModelDefId(StateModelDefId stateModelDefId) {
        setStateModelDefRef(stateModelDefId.stringify());
    }

    /**
     * Set the number of partitions of this resource
     * @param numPartitions the number of partitions
     */
    public void setNumPartitions(int numPartitions) {
        _record.setIntField(IdealStateProperty.NUM_PARTITIONS.toString(), numPartitions);
    }

    /**
     * Get the number of partitions of this resource
     * @return the number of partitions
     */
    public int getNumPartitions() {
        return _record.getIntField(IdealStateProperty.NUM_PARTITIONS.toString(), -1);
    }

    /**
     * Set the number of replicas for each partition of this resource. There are documented special
     * values for the replica count, so this is a String.
     * @param replicas replica count (as a string)
     */
    public void setReplicas(String replicas) {
        _record.setSimpleField(IdealStateProperty.REPLICAS.toString(), replicas);
    }

    /**
     * Get the number of replicas for each partition of this resource
     * @return number of replicas (as a string)
     */
    public String getReplicas() {
        // HACK: if replica doesn't exists, use the length of the first list field
        // instead
        // TODO: remove it when Dbus fixed the IdealState writer
        String replica = _record.getSimpleField(IdealStateProperty.REPLICAS.toString());
        if (replica == null) {
            String firstPartition = null;
            switch (getRebalanceMode()) {
            case SEMI_AUTO:
                if (_record.getListFields().size() == 0) {
                    replica = "0";
                } else {
                    firstPartition = new ArrayList<String>(_record.getListFields().keySet()).get(0);
                    replica = Integer
                            .toString(firstPartition == null ? 0 : _record.getListField(firstPartition).size());
                }
                logger.warn(
                        "could NOT find number of replicas in idealState. Use size of the first list instead. replica: "
                                + replica + ", 1st partition: " + firstPartition);
                break;
            case CUSTOMIZED:
                if (_record.getMapFields().size() == 0) {
                    replica = "0";
                } else {
                    firstPartition = new ArrayList<String>(_record.getMapFields().keySet()).get(0);
                    replica = Integer
                            .toString(firstPartition == null ? 0 : _record.getMapField(firstPartition).size());
                }
                logger.warn("could NOT find replicas in idealState. Use size of the first map instead. replica: "
                        + replica + ", 1st partition: " + firstPartition);
                break;
            default:
                replica = "0";
                logger.warn("could NOT determine replicas. set to 0");
                break;
            }
        }

        return replica;
    }

    /**
     * Set the state model factory associated with this resource
     * @param name state model factory name
     */
    public void setStateModelFactoryName(String name) {
        _record.setSimpleField(IdealStateProperty.STATE_MODEL_FACTORY_NAME.toString(), name);
    }

    /**
     * Set the state model factory associated with this resource
     * @param name state model factory id
     */
    public void setStateModelFactoryId(StateModelFactoryId stateModelFactoryId) {
        if (stateModelFactoryId != null) {
            setStateModelFactoryName(stateModelFactoryId.stringify());
        }
    }

    /**
     * Get the state model factory associated with this resource
     * @return state model factory name
     */
    public String getStateModelFactoryName() {
        return _record.getStringField(IdealStateProperty.STATE_MODEL_FACTORY_NAME.toString(),
                HelixConstants.DEFAULT_STATE_MODEL_FACTORY);
    }

    /**
     * Get the state model factory associated with this resource
     * @return state model factory id
     */
    public StateModelFactoryId getStateModelFactoryId() {
        return StateModelFactoryId.from(getStateModelFactoryName());
    }

    /**
     * Set the frequency with which to rebalance
     * @return the rebalancing timer period
     */
    public int getRebalanceTimerPeriod() {
        return _record.getIntField(IdealStateProperty.REBALANCE_TIMER_PERIOD.toString(), -1);
    }

    @Override
    public boolean isValid() {
        if (getNumPartitions() < 0) {
            logger.error("idealState:" + _record + " does not have number of partitions (was " + getNumPartitions()
                    + ").");
            return false;
        }

        if (getStateModelDefRef() == null) {
            logger.error("idealStates:" + _record + " does not have state model definition.");
            return false;
        }

        if (getRebalanceMode() == RebalanceMode.SEMI_AUTO) {
            String replicaStr = getReplicas();
            if (replicaStr == null) {
                logger.error("invalid ideal-state. missing replicas in auto mode. record was: " + _record);
                return false;
            }

            if (!replicaStr.equals(HelixConstants.StateModelToken.ANY_LIVEINSTANCE.toString())) {
                int replica = Integer.parseInt(replicaStr);
                Set<String> partitionSet = getPartitionSet();
                for (String partition : partitionSet) {
                    List<String> preferenceList = getPreferenceList(partition);
                    if (preferenceList == null || preferenceList.size() != replica) {
                        logger.error(
                                "invalid ideal-state. preference-list size not equals to replicas in auto mode. replica: "
                                        + replica + ", preference-list size: " + preferenceList.size()
                                        + ", record was: " + _record);
                        return false;
                    }
                }
            }
        }

        return true;
    }

    /**
     * Set a tag to check to enforce assignment to certain instances
     * @param groupTag the instance group tag
     */
    public void setInstanceGroupTag(String groupTag) {
        _record.setSimpleField(IdealStateProperty.INSTANCE_GROUP_TAG.toString(), groupTag);
    }

    /**
     * Check for a tag that will restrict assignment to instances with a matching tag
     * @return the group tag, or null if none is present
     */
    public String getInstanceGroupTag() {
        return _record.getSimpleField(IdealStateProperty.INSTANCE_GROUP_TAG.toString());
    }

    /**
     * Update the ideal state from a ResourceAssignment computed during a rebalance
     * @param assignment the new resource assignment
     * @param stateModelDef state model of the resource
     */
    public void updateFromAssignment(ResourceAssignment assignment, StateModelDefinition stateModelDef) {
        // clear all preference lists and maps
        _record.getMapFields().clear();
        _record.getListFields().clear();

        // assign a partition at a time
        for (PartitionId partition : assignment.getMappedPartitionIds()) {
            List<ParticipantId> preferenceList = new ArrayList<ParticipantId>();
            Map<ParticipantId, State> participantStateMap = new HashMap<ParticipantId, State>();

            // invert the map to get in state order
            Map<ParticipantId, State> replicaMap = assignment.getReplicaMap(partition);
            ListMultimap<State, ParticipantId> inverseMap = ArrayListMultimap.create();
            Multimaps.invertFrom(Multimaps.forMap(replicaMap), inverseMap);

            // update the ideal state in order of state priorities
            for (State state : stateModelDef.getTypedStatesPriorityList()) {
                if (!state.equals(State.from(HelixDefinedState.DROPPED))
                        && !state.equals(State.from(HelixDefinedState.ERROR))) {
                    List<ParticipantId> stateParticipants = inverseMap.get(state);
                    for (ParticipantId participant : stateParticipants) {
                        preferenceList.add(participant);
                        participantStateMap.put(participant, state);
                    }
                }
            }
            setPreferenceList(partition, preferenceList);
            setParticipantStateMap(partition, participantStateMap);
        }
    }

    private RebalanceMode normalizeRebalanceMode(IdealStateModeProperty mode) {
        RebalanceMode property;
        switch (mode) {
        case AUTO_REBALANCE:
            property = RebalanceMode.FULL_AUTO;
            break;
        case AUTO:
            property = RebalanceMode.SEMI_AUTO;
            break;
        case CUSTOMIZED:
            property = RebalanceMode.CUSTOMIZED;
            break;
        default:
            String rebalancerName = getRebalancerClassName();
            if (rebalancerName == null) {
                property = RebalanceMode.SEMI_AUTO;
            } else if (rebalancerName.equals(FixedTargetTaskRebalancer.class.getName())
                    || rebalancerName.equals(GenericTaskRebalancer.class.getName())) {
                property = RebalanceMode.TASK;
            } else {
                property = RebalanceMode.USER_DEFINED;
            }
            break;
        }
        return property;
    }

    private IdealStateModeProperty denormalizeRebalanceMode(RebalanceMode rebalancerType) {
        IdealStateModeProperty property;
        switch (rebalancerType) {
        case FULL_AUTO:
            property = IdealStateModeProperty.AUTO_REBALANCE;
            break;
        case SEMI_AUTO:
            property = IdealStateModeProperty.AUTO;
            break;
        case CUSTOMIZED:
            property = IdealStateModeProperty.CUSTOMIZED;
            break;
        default:
            property = IdealStateModeProperty.AUTO;
            break;
        }
        return property;
    }

    /**
     * Parse a rebalance mode from a string. It can also understand IdealStateModeProperty values
     * @param mode string containing a RebalanceMode value
     * @param defaultMode the mode to use if the string is not valid
     * @return converted RebalanceMode value
     */
    public RebalanceMode rebalanceModeFromString(String mode, RebalanceMode defaultMode) {
        RebalanceMode rebalanceMode = defaultMode;
        try {
            rebalanceMode = RebalanceMode.valueOf(mode);
        } catch (Exception rebalanceModeException) {
            try {
                IdealStateModeProperty oldMode = IdealStateModeProperty.valueOf(mode);
                rebalanceMode = normalizeRebalanceMode(oldMode);
            } catch (Exception e) {
                logger.error(e);
            }
        }
        return rebalanceMode;
    }

    /**
     * Get the non-Helix simple fields from this property and add them to a UserConfig
     * @param userConfig the user config to update
     */
    public void updateUserConfig(UserConfig userConfig) {
        try {
            for (String simpleField : _record.getSimpleFields().keySet()) {
                Optional<IdealStateProperty> enumField = Enums.getIfPresent(IdealStateProperty.class, simpleField);
                if (!simpleField.contains(NamespacedConfig.PREFIX_CHAR + "") && !enumField.isPresent()) {
                    userConfig.setSimpleField(simpleField, _record.getSimpleField(simpleField));
                }
            }
        } catch (NoSuchMethodError e) {
            LOG.error("Could not update user config", e);
        }
    }

    /**
     * Convert a preference list of strings into a preference list of participants
     * @param rawPreferenceList the list of strings representing participant names
     * @return converted list
     */
    public static List<ParticipantId> preferenceListFromStringList(List<String> rawPreferenceList) {
        if (rawPreferenceList == null) {
            return Collections.emptyList();
        }
        return Lists.transform(new ArrayList<String>(rawPreferenceList), new Function<String, ParticipantId>() {
            @Override
            public ParticipantId apply(String participantName) {
                return ParticipantId.from(participantName);
            }
        });
    }

    /**
     * Convert preference lists of strings into preference lists of participants
     * @param rawPreferenceLists a map of partition name to a list of participant names
     * @return converted lists as a map
     */
    public static Map<? extends PartitionId, List<ParticipantId>> preferenceListsFromStringLists(
            Map<String, List<String>> rawPreferenceLists) {
        if (rawPreferenceLists == null) {
            return Collections.emptyMap();
        }
        Map<PartitionId, List<ParticipantId>> preferenceLists = new HashMap<PartitionId, List<ParticipantId>>();
        for (String partitionId : rawPreferenceLists.keySet()) {
            preferenceLists.put(PartitionId.from(partitionId),
                    preferenceListFromStringList(rawPreferenceLists.get(partitionId)));
        }
        return preferenceLists;
    }

    /**
     * Convert a preference list of participants into a preference list of strings
     * @param preferenceList the list of strings representing participant ids
     * @return converted list
     */
    public static List<String> stringListFromPreferenceList(List<ParticipantId> preferenceList) {
        if (preferenceList == null) {
            return Collections.emptyList();
        }
        return Lists.transform(new ArrayList<ParticipantId>(preferenceList), new Function<ParticipantId, String>() {
            @Override
            public String apply(ParticipantId participantId) {
                return participantId.stringify();
            }
        });
    }

    /**
     * Convert preference lists of participants into preference lists of strings
     * @param preferenceLists a map of partition id to a list of participant ids
     * @return converted lists as a map
     */
    public static Map<String, List<String>> stringListsFromPreferenceLists(
            Map<PartitionId, List<ParticipantId>> preferenceLists) {
        if (preferenceLists == null) {
            return Collections.emptyMap();
        }
        Map<String, List<String>> rawPreferenceLists = new HashMap<String, List<String>>();
        for (PartitionId partitionId : preferenceLists.keySet()) {
            rawPreferenceLists.put(partitionId.stringify(),
                    stringListFromPreferenceList(preferenceLists.get(partitionId)));
        }
        return rawPreferenceLists;
    }

    /**
     * Convert a partition mapping as strings into a participant state map
     * @param rawMap the map of participant name to state
     * @return converted map
     */
    public static Map<ParticipantId, State> participantStateMapFromStringMap(Map<String, String> rawMap) {
        return ResourceAssignment.replicaMapFromStringMap(rawMap);
    }

    /**
     * Convert a full state mapping as strings into participant state maps
     * @param rawMaps the map of partition name to participant name and state
     * @return converted maps
     */
    public static Map<? extends PartitionId, Map<ParticipantId, State>> participantStateMapsFromStringMaps(
            Map<String, Map<String, String>> rawMaps) {
        return ResourceAssignment.replicaMapsFromStringMaps(rawMaps);
    }

    /**
     * Convert a partition mapping into a mapping of string names
     * @param participantStateMap the map of participant id to state
     * @return converted map
     */
    public static Map<String, String> stringMapFromParticipantStateMap(
            Map<ParticipantId, State> participantStateMap) {
        return ResourceAssignment.stringMapFromReplicaMap(participantStateMap);
    }

    /**
     * Convert a full state mapping into a mapping of string names
     * @param participantStateMaps the map of partition id to participant id and state
     * @return converted maps
     */
    public static Map<String, Map<String, String>> stringMapsFromParticipantStateMaps(
            Map<PartitionId, Map<ParticipantId, State>> participantStateMaps) {
        return ResourceAssignment.stringMapsFromReplicaMaps(participantStateMaps);
    }

    /**
     * Get if the resource is enabled or not
     * By default, it's enabled
     * @return true if enabled; false otherwise
     */
    public boolean isEnabled() {
        return _record.getBooleanField(IdealStateProperty.HELIX_ENABLED.name(), true);
    }

    /**
     * Enable/Disable the resource
     * @param enabled
     */
    public void enable(boolean enabled) {
        _record.setSimpleField(IdealStateProperty.HELIX_ENABLED.name(), Boolean.toString(enabled));
    }
}