/*
 * 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.geode.internal.cache;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.logging.log4j.Logger;

import org.apache.geode.DataSerializer;
import org.apache.geode.cache.CacheEvent;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.InterestPolicy;
import org.apache.geode.cache.RegionDestroyedException;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.SubscriptionAttributes;
import org.apache.geode.distributed.Role;
import org.apache.geode.distributed.internal.DistributionAdvisor;
import org.apache.geode.distributed.internal.DistributionManager;
import org.apache.geode.distributed.internal.MembershipListener;
import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
import org.apache.geode.internal.Assert;
import org.apache.geode.internal.DSCODE;
import org.apache.geode.internal.InternalDataSerializer;
import org.apache.geode.internal.cache.partitioned.PRLocallyDestroyedException;
import org.apache.geode.internal.cache.persistence.DiskStoreID;
import org.apache.geode.internal.cache.persistence.PersistentMemberID;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.internal.logging.log4j.LogMarker;

/**
 * Adds bookkeeping info and cache-specific behavior to DistributionAdvisor. Adds bit-encoded flags
 * in addition to object info.
 *
 */
@SuppressWarnings("deprecation")
public class CacheDistributionAdvisor extends DistributionAdvisor {

  private static final Logger logger = LogService.getLogger();

  // moved ROLLOVER_* constants to DistributionAdvisor

  /** bit masks */
  private static final int INTEREST_MASK = 0x01;
  private static final int REPLICATE_MASK = 0x02;
  private static final int LOADER_MASK = 0x04;
  private static final int WRITER_MASK = 0x08;
  private static final int LISTENER_MASK = 0x10;
  private static final int DIST_ACK_MASK = 0x20;
  private static final int GLOBAL_MASK = 0x40;
  private static final int IN_RECOVERY_MASK = 0x80;
  private static final int PERSISTENT_MASK = 0x100;
  private static final int PROXY_MASK = 0x200;
  private static final int PRELOADED_MASK = 0x400;
  private static final int IS_PARTITIONED_MASK = 0x800;
  private static final int REGION_INITIALIZED_MASK = 0x1000;
  private static final int IS_GATEWAY_ENABLED_MASK = 0x2000;
  // provider is no longer a supported attribute.
  // private static final int IS_GII_PROVIDER_MASK = 0x4000;
  private static final int PERSISTENT_ID_MASK = 0x4000;
  /** does this member require operation notification (PartitionedRegions) */
  protected static final int REQUIRES_NOTIFICATION_MASK = 0x8000;
  private static final int HAS_CACHE_SERVER_MASK = 0x10000;
  private static final int REQUIRES_OLD_VALUE_MASK = 0x20000;
  // unused 0x40000;
  private static final int PERSISTENCE_INITIALIZED_MASK = 0x80000;
  // Important below mentioned bit masks are not available
  /**
   * Using following masks for gatewaysender queue startup polic informations.
   */
  // private static final int HUB_STARTUP_POLICY_MASK = 0x07<<20;

  private static final int GATEWAY_SENDER_IDS_MASK = 0x200000;

  private static final int ASYNC_EVENT_QUEUE_IDS_MASK = 0x400000;
  private static final int IS_OFF_HEAP_MASK = 0x800000;
  private static final int CACHE_SERVICE_PROFILES_MASK = 0x1000000;


  // moved initializ* to DistributionAdvisor

  // moved membershipVersion to DistributionAdvisor

  // moved previousVersionOpCount to DistributionAdvisor
  // moved currentVersionOpCount to DistributionAdvisor

  // moved removedProfiles to DistributionAdvisor

  /** Creates a new instance of CacheDistributionAdvisor */
  protected CacheDistributionAdvisor(CacheDistributionAdvisee region) {
    super(region);
  }

  public static CacheDistributionAdvisor createCacheDistributionAdvisor(
      CacheDistributionAdvisee region) {
    CacheDistributionAdvisor advisor = new CacheDistributionAdvisor(region);
    advisor.initialize();
    return advisor;
  }

  @Override
  public String toString() {
    return "CacheDistributionAdvisor for region " + getAdvisee().getFullPath();
  }

  // moved toStringWithProfiles to DistributionAdvisor

  // moved initializationGate to DistributionAdvisor

  // moved isInitialized to DistributionAdvisor

  // moved addMembershipListenerAndAdviseGeneric to DistributionAdvisor

  /**
   * Returns a the set of members that either want all events or are caching data.
   * 
   * @param excludeInRecovery if true then members in recovery are excluded
   */
  private Set adviseAllEventsOrCached(final boolean excludeInRecovery)
      throws IllegalStateException {
    getAdvisee().getCancelCriterion().checkCancelInProgress(null);
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        if (excludeInRecovery && cp.inRecovery) {
          return false;
        }
        return cp.cachedOrAllEventsWithListener();
      }
    });
  }

  /**
   * Provide recipient information for an update or create operation.
   *
   */
  Set adviseUpdate(final EntryEventImpl event) throws IllegalStateException {
    if (event.hasNewValue() || event.getOperation().isPutAll()) {
      // only need to distribute it to guys that want all events or cache data
      return adviseAllEventsOrCached(true/* fixes 41147 */);
    } else {
      // The new value is null so this is a create with a null value,
      // in which case we only need to distribute this message to replicates
      // or all events that are not a proxy or if a proxy has a listener
      return adviseFilter(new Filter() {
        public boolean include(Profile profile) {
          assert profile instanceof CacheProfile;
          CacheProfile cp = (CacheProfile) profile;
          DataPolicy dp = cp.dataPolicy;
          return dp.withReplication()
              || (cp.allEvents() && (dp.withStorage() || cp.hasCacheListener));
        }
      });
    }
  }

  /**
   * Provide recipient information for TX lock and commit.
   * 
   * @return Set of Serializable members that the current transaction will be distributed to.
   *         Currently this is any other member who has this region defined. No reference to Set
   *         kept by advisor so caller is free to modify it
   */
  public Set<InternalDistributedMember> adviseTX() throws IllegalStateException {

    boolean isMetaDataWithTransactions = getAdvisee() instanceof LocalRegion
        && ((LocalRegion) getAdvisee()).isMetaRegionWithTransactions();

    Set<InternalDistributedMember> badList = Collections.emptySet();
    if (!TXManagerImpl.ALLOW_PERSISTENT_TRANSACTIONS && !isMetaDataWithTransactions) {
      badList = adviseFilter(new Filter() {
        public boolean include(Profile profile) {
          assert profile instanceof CacheProfile;
          CacheProfile prof = (CacheProfile) profile;
          return (prof.isPersistent());
        }
      });
    }
    if (badList.isEmpty()) {
      return adviseFilter(new Filter() {
        public boolean include(Profile profile) {
          assert profile instanceof CacheProfile;
          CacheProfile cp = (CacheProfile) profile;
          return cp.cachedOrAllEvents();
        }
      });
    } else {
      StringBuffer badIds = new StringBuffer();
      Iterator biI = badList.iterator();
      while (biI.hasNext()) {
        badIds.append(biI.next().toString());
        if (biI.hasNext())
          badIds.append(", ");
      }
      throw new IllegalStateException(
          LocalizedStrings.CacheDistributionAdvisor_ILLEGAL_REGION_CONFIGURATION_FOR_MEMBERS_0
              .toLocalizedString(badIds.toString()));
    }
  }

  /**
   * Provide recipient information for netLoad
   * 
   * @return Set of Serializable members that have a CacheLoader installed; no reference to Set kept
   *         by advisor so caller is free to modify it
   */
  public Set adviseNetLoad() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile prof = (CacheProfile) profile;

        // if region in cache is not yet initialized, exclude
        if (!prof.regionInitialized) { // fix for bug 41102
          return false;
        }

        return prof.hasCacheLoader;
      }
    });
  }

  public FilterRoutingInfo adviseFilterRouting(CacheEvent event, Set cacheOpRecipients) {
    FilterProfile fp = ((LocalRegion) event.getRegion()).getFilterProfile();
    if (fp != null) {
      return fp.getFilterRoutingInfoPart1(event, this.profiles, cacheOpRecipients);
    }
    return null;
  }


  /**
   * Same as adviseGeneric except in recovery excluded.
   */
  public Set adviseCacheOp() {
    return adviseAllEventsOrCached(true);
  }

  /**
   * Same as adviseCacheOp but only includes members that are playing the specified role.
   * 
   * @since GemFire 5.0
   */
  public Set adviseCacheOpRole(final Role role) {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        // if region in cache is not yet initialized, exclude
        if (!cp.regionInitialized) {
          return false;
        }
        if (!cp.cachedOrAllEventsWithListener()) {
          return false;
        }
        return cp.getDistributedMember().getRoles().contains(role);
      }
    });
  }


  /*
   * * Same as adviseGeneric but excludes guys in recover
   */
  public Set adviseInvalidateRegion() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        return !cp.inRecovery;
      }
    });
  }


  /**
   * Same as adviseGeneric
   */
  public Set adviseDestroyRegion() {
    return adviseGeneric();
  }

  /**
   * Provide recipient information for netWrite
   * 
   * @return Set of Serializable member ids that have a CacheWriter installed; no reference to Set
   *         kept by advisor so caller is free to modify it
   */
  public Set adviseNetWrite() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile prof = (CacheProfile) profile;
        // if region in cache is in recovery, exclude
        if (prof.inRecovery) {
          return false;
        }

        return prof.hasCacheWriter;
      }
    });
  }

  public Set<InternalDistributedMember> adviseInitializedReplicates() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        if (cp.dataPolicy.withReplication() && cp.regionInitialized) {
          return true;
        }
        return false;
      }
    });
  }

  /**
   * Provide recipient information for netSearch
   * 
   * @return Set of Serializable member ids that have the region and are have storage (no need to
   *         search an empty cache)
   */
  public Set adviseNetSearch() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        // if region in cache is not yet initialized, exclude
        if (!cp.regionInitialized) {
          return false;
        }
        DataPolicy dp = cp.dataPolicy;
        return dp.withStorage();
      }
    });
  }

  // moved dumpProfiles to DistributionAdvisor

  public InitialImageAdvice adviseInitialImage(InitialImageAdvice previousAdvice) {
    return adviseInitialImage(previousAdvice, false);
  }

  @SuppressWarnings("synthetic-access")
  public InitialImageAdvice adviseInitialImage(InitialImageAdvice previousAdvice,
      boolean persistent) {
    initializationGate();

    if (logger.isTraceEnabled(LogMarker.DA)) {
      dumpProfiles("AdviseInitialImage");
    }

    Profile[] allProfiles = this.profiles; // volatile read
    if (allProfiles.length == 0) {
      return new InitialImageAdvice();
    }

    Set<InternalDistributedMember> replicates = new HashSet<InternalDistributedMember>();
    Set<InternalDistributedMember> others = new HashSet<InternalDistributedMember>();
    Set<InternalDistributedMember> preloaded = new HashSet<InternalDistributedMember>();
    Set<InternalDistributedMember> empties = new HashSet<InternalDistributedMember>();
    Set<InternalDistributedMember> uninitialized = new HashSet<InternalDistributedMember>();
    Set<InternalDistributedMember> nonPersistent = new HashSet<InternalDistributedMember>();

    Map<InternalDistributedMember, CacheProfile> memberProfiles =
        new HashMap<InternalDistributedMember, CacheProfile>();

    for (int i = 0; i < allProfiles.length; i++) {
      CacheProfile profile = (CacheProfile) allProfiles[i];

      // Make sure that we don't return a member that was in the previous initial image advice.
      // Unless that member has changed it's profile since the last time we checked.
      if (previousAdvice != null) {
        CacheProfile previousProfile =
            previousAdvice.memberProfiles.get(profile.getDistributedMember());
        if (previousProfile != null
            && previousProfile.getSerialNumber() == profile.getSerialNumber()
            && previousProfile.getVersion() == profile.getVersion()) {
          continue;
        }
      }

      // if region in cache is in recovery, exclude
      if (profile.inRecovery) {
        uninitialized.add(profile.getDistributedMember());
        continue;
      }
      // No need to do a GII from uninitialized member.
      if (!profile.regionInitialized) {
        uninitialized.add(profile.getDistributedMember());
        continue;
      }

      if (profile.dataPolicy.withReplication()) {
        if (!persistent || profile.dataPolicy.withPersistence()) {
          // If the local member is persistent, we only want
          // to include persistent replicas in the set of replicates.
          replicates.add(profile.getDistributedMember());
        } else {
          nonPersistent.add(profile.getDistributedMember());
        }
        memberProfiles.put(profile.getDistributedMember(), profile);
      } else if (profile.dataPolicy.isPreloaded()) {
        preloaded.add(profile.getDistributedMember());
        memberProfiles.put(profile.getDistributedMember(), profile);
      } else if (profile.dataPolicy.withStorage()) {
        // don't bother asking proxy members for initial image
        others.add(profile.getDistributedMember());
        memberProfiles.put(profile.getDistributedMember(), profile);
      } else {
        empties.add(profile.getDistributedMember());
      }
    }

    InitialImageAdvice advice = new InitialImageAdvice(replicates, others, preloaded, empties,
        uninitialized, nonPersistent, memberProfiles);

    if (logger.isDebugEnabled()) {
      logger.debug(advice);
    }
    return advice;
  }

  /**
   * returns the set of all the members in the system which requires old values and are not yet
   * finished with initialization (including GII).
   * 
   * @since GemFire 5.5
   */
  public Set adviseRequiresOldValueInCacheOp() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        return cp.requiresOldValueInEvents && !cp.regionInitialized;
      }
    });
  }


  // moved adviseProfileExchange to DistributionAdvisor

  // moved getProfile to DistributionAdvisor

  // moved exchangeProfiles to DistributionAdvisor

  // moved getDistributionManager to DistributionAdvisor

  /** Instantiate new distribution profile for this member */
  @Override
  protected Profile instantiateProfile(InternalDistributedMember memberId, int version) {
    return new CacheProfile(memberId, version);
  }

  @Override
  protected boolean evaluateProfiles(Profile newProfile, Profile oldProfile) {
    boolean result = super.evaluateProfiles(newProfile, oldProfile);
    if (result) {
      CacheProfile newCP = (CacheProfile) newProfile;
      CacheProfile oldCP = (CacheProfile) oldProfile;
      if ((oldCP == null || !oldCP.regionInitialized) && newCP.regionInitialized) {
        // invoke membership listeners, if any
        CacheDistributionAdvisee advisee = (CacheDistributionAdvisee) getAdvisee();
        advisee.remoteRegionInitialized(newCP);
      }
    }
    return result;
  }

  /**
   * Profile information for a remote counterpart.
   */
  public static class CacheProfile extends DistributionAdvisor.Profile {
    public DataPolicy dataPolicy = DataPolicy.REPLICATE;
    public InterestPolicy interestPolicy = InterestPolicy.DEFAULT;
    public boolean hasCacheLoader = false;
    public boolean hasCacheWriter = false;
    public boolean hasCacheListener = false;
    public Scope scope = Scope.DISTRIBUTED_NO_ACK;
    public boolean inRecovery = false;
    public Set<String> gatewaySenderIds = Collections.emptySet();
    public Set<String> asyncEventQueueIds = Collections.emptySet();
    /**
     * Will be null if the profile doesn't need to have the attributes
     */
    public SubscriptionAttributes subscriptionAttributes = null;

    public boolean isPartitioned = false;
    public boolean isGatewayEnabled = false;
    public boolean isPersistent = false;

    public boolean isOffHeap = false;

    // moved initialMembershipVersion to DistributionAdvisor.Profile
    // moved serialNumber to DistributionAdvisor.Profile

    /**
     * this member's client interest / continuous query profile. This is used for event processing
     * to reduce the number of times CQs are executed and to have the originating member for an
     * event pay the cpu cost of executing filters on the event.
     */
    public FilterProfile filterProfile;

    /**
     * Some cache listeners require old values in cache operation messages, at least during GII
     */
    public boolean requiresOldValueInEvents;

    /**
     * Whether the region has completed initialization, including GII. This information may be
     * incorrect for a PartitionedRegion, but may be relied upon for DistributedRegions (including
     * BucketRegions)
     * 
     * @since GemFire prpersist this field is now overloaded for partitioned regions with
     *        persistence. In the case of pr persistence, this field indicates that the region has
     *        finished recovery from disk.
     */
    public boolean regionInitialized;


    /**
     * True when a members persistent store is initialized. Note that regionInitialized may be true
     * when this is false in the case of createBucketAtomically. With createBucketAtomically, the
     * peristent store is not created until the EndBucketCreationMessage is sent.
     */
    public boolean persistenceInitialized;

    public PersistentMemberID persistentID;

    /**
     * This member has any cache servers. This is not actively maintained for local profiles (i.e.,
     * a profile representing this vm)
     */
    public boolean hasCacheServer = false;

    public List<CacheServiceProfile> cacheServiceProfiles = new ArrayList<>();

    /** for internal use, required for DataSerializer.readObject */
    public CacheProfile() {}

    /** used for routing computation */
    public CacheProfile(FilterProfile localProfile) {
      this.filterProfile = localProfile;
    }

    public CacheProfile(InternalDistributedMember memberId, int version) {
      super(memberId, version);
    }

    public CacheProfile(CacheProfile toCopy) {
      super(toCopy.getDistributedMember(), toCopy.version);
      setIntInfo(toCopy.getIntInfo());
    }

    /** Return the profile data information that can be stored in an int */
    protected int getIntInfo() {
      int s = 0;
      if (this.dataPolicy.withReplication()) {
        s |= REPLICATE_MASK;
        if (this.dataPolicy.isPersistentReplicate()) {
          s |= PERSISTENT_MASK;
        }
      } else {
        if (this.dataPolicy.isEmpty())
          s |= PROXY_MASK;
        if (this.dataPolicy.isPreloaded())
          s |= PRELOADED_MASK;
      }
      if (this.subscriptionAttributes != null
          && this.subscriptionAttributes.getInterestPolicy().isAll()) {
        s |= INTEREST_MASK;
      }
      if (this.hasCacheLoader)
        s |= LOADER_MASK;
      if (this.hasCacheWriter)
        s |= WRITER_MASK;
      if (this.hasCacheListener)
        s |= LISTENER_MASK;
      if (this.scope.isDistributedAck())
        s |= DIST_ACK_MASK;
      if (this.scope.isGlobal())
        s |= GLOBAL_MASK;
      if (this.inRecovery)
        s |= IN_RECOVERY_MASK;
      if (this.isPartitioned)
        s |= IS_PARTITIONED_MASK;
      if (this.isGatewayEnabled)
        s |= IS_GATEWAY_ENABLED_MASK;
      if (this.isPersistent)
        s |= PERSISTENT_MASK;
      if (this.regionInitialized)
        s |= REGION_INITIALIZED_MASK;
      if (this.persistentID != null)
        s |= PERSISTENT_ID_MASK;
      if (this.hasCacheServer)
        s |= HAS_CACHE_SERVER_MASK;
      if (this.requiresOldValueInEvents)
        s |= REQUIRES_OLD_VALUE_MASK;
      if (this.persistenceInitialized)
        s |= PERSISTENCE_INITIALIZED_MASK;
      if (!this.gatewaySenderIds.isEmpty())
        s |= GATEWAY_SENDER_IDS_MASK;
      if (!this.asyncEventQueueIds.isEmpty())
        s |= ASYNC_EVENT_QUEUE_IDS_MASK;
      if (this.isOffHeap)
        s |= IS_OFF_HEAP_MASK;
      if (!this.cacheServiceProfiles.isEmpty())
        s |= CACHE_SERVICE_PROFILES_MASK;
      Assert.assertTrue(!this.scope.isLocal());
      return s;
    }

    private boolean hasGatewaySenderIds(int bits) {
      return (bits & GATEWAY_SENDER_IDS_MASK) != 0;
    }

    private boolean hasAsyncEventQueueIds(int bits) {
      return (bits & ASYNC_EVENT_QUEUE_IDS_MASK) != 0;
    }

    /**
     * @param bits
     * @return true if the serialized message has a persistentID
     */
    private boolean hasPersistentID(int bits) {
      return (bits & PERSISTENT_ID_MASK) != 0;
    }

    public boolean isPersistent() {
      return this.dataPolicy.withPersistence();
    }

    /** Set the profile data information that is stored in a short */
    protected void setIntInfo(int s) {
      if ((s & REPLICATE_MASK) != 0) {
        if ((s & PERSISTENT_MASK) != 0) {
          this.dataPolicy = DataPolicy.PERSISTENT_REPLICATE;
        } else {
          this.dataPolicy = DataPolicy.REPLICATE;
        }
      } else if ((s & PROXY_MASK) != 0) {
        this.dataPolicy = DataPolicy.EMPTY;
      } else if ((s & PRELOADED_MASK) != 0) {
        this.dataPolicy = DataPolicy.PRELOADED;
      } else { // CACHED
        this.dataPolicy = DataPolicy.NORMAL;
      }

      if ((s & IS_PARTITIONED_MASK) != 0) {
        if ((s & PERSISTENT_MASK) != 0) {
          this.dataPolicy = DataPolicy.PERSISTENT_PARTITION;
        } else {
          this.dataPolicy = DataPolicy.PARTITION;
        }
      }

      if ((s & INTEREST_MASK) != 0) {
        this.subscriptionAttributes = new SubscriptionAttributes(InterestPolicy.ALL);
      } else {
        this.subscriptionAttributes = new SubscriptionAttributes(InterestPolicy.CACHE_CONTENT);
      }
      this.hasCacheLoader = (s & LOADER_MASK) != 0;
      this.hasCacheWriter = (s & WRITER_MASK) != 0;
      this.hasCacheListener = (s & LISTENER_MASK) != 0;
      this.scope = (s & DIST_ACK_MASK) != 0 ? Scope.DISTRIBUTED_ACK
          : ((s & GLOBAL_MASK) != 0 ? Scope.GLOBAL : Scope.DISTRIBUTED_NO_ACK);
      this.inRecovery = (s & IN_RECOVERY_MASK) != 0;
      this.isPartitioned = (s & IS_PARTITIONED_MASK) != 0;
      this.isGatewayEnabled = (s & IS_GATEWAY_ENABLED_MASK) != 0;
      this.isPersistent = (s & PERSISTENT_MASK) != 0;
      this.regionInitialized = ((s & REGION_INITIALIZED_MASK) != 0);
      this.hasCacheServer = ((s & HAS_CACHE_SERVER_MASK) != 0);
      this.requiresOldValueInEvents = ((s & REQUIRES_OLD_VALUE_MASK) != 0);
      this.persistenceInitialized = (s & PERSISTENCE_INITIALIZED_MASK) != 0;
      this.isOffHeap = (s & IS_OFF_HEAP_MASK) != 0;
    }

    /**
     * Sets the SubscriptionAttributes for the region that this profile is on
     * 
     * @since GemFire 5.0
     */
    public void setSubscriptionAttributes(SubscriptionAttributes sa) {
      this.subscriptionAttributes = sa;
    }

    /**
     * Return true if cached or allEvents and a listener
     */
    public boolean cachedOrAllEventsWithListener() {
      // to fix bug 36804 to ignore hasCacheListener
      // return this.dataPolicy.withStorage() ||
      // (allEvents() && this.hasCacheListener);
      return cachedOrAllEvents();
    }

    /**
     * Return true if cached or allEvents
     */
    public boolean cachedOrAllEvents() {
      return this.dataPolicy.withStorage() || allEvents();
    }

    /**
     * Return true if subscribed to all events
     */
    public boolean allEvents() {
      return this.subscriptionAttributes.getInterestPolicy().isAll();
    }

    public void addCacheServiceProfile(CacheServiceProfile profile) {
      this.cacheServiceProfiles.add(profile);
    }

    private boolean hasCacheServiceProfiles(int bits) {
      return (bits & CACHE_SERVICE_PROFILES_MASK) != 0;
    }

    /**
     * Used to process an incoming cache profile.
     */
    @Override
    public void processIncoming(DistributionManager dm, String adviseePath, boolean removeProfile,
        boolean exchangeProfiles, final List<Profile> replyProfiles) {
      try {
        Assert.assertTrue(adviseePath != null, "adviseePath was null");

        LocalRegion lclRgn;
        int oldLevel = LocalRegion.setThreadInitLevelRequirement(LocalRegion.ANY_INIT);
        try {
          lclRgn = LocalRegion.getRegionFromPath(dm.getSystem(), adviseePath);
        } finally {
          LocalRegion.setThreadInitLevelRequirement(oldLevel);
        }

        if (lclRgn instanceof CacheDistributionAdvisee) {
          if (lclRgn.isUsedForPartitionedRegionBucket()) {
            if (!((BucketRegion) lclRgn).isPartitionedRegionOpen()) {
              return;
            }
          }
          handleCacheDistributionAdvisee((CacheDistributionAdvisee) lclRgn, adviseePath,
              removeProfile, exchangeProfiles, true, replyProfiles);
        } else {
          if (lclRgn == null) {
            handleCacheDistributionAdvisee(
                PartitionedRegionHelper.getProxyBucketRegion(GemFireCacheImpl.getInstance(),
                    adviseePath, false),
                adviseePath, removeProfile, exchangeProfiles, false, replyProfiles);
          } else {
            if (logger.isDebugEnabled()) {
              logger.debug("While processing UpdateAttributes message, region has local scope: {}",
                  adviseePath);
            }
          }
        }
      } catch (PRLocallyDestroyedException fre) {
        if (logger.isDebugEnabled()) {
          logger.debug("<Region Locally destroyed> /// {}", this);
        }
      } catch (RegionDestroyedException e) {
        if (logger.isDebugEnabled()) {
          logger.debug("<region destroyed> /// {}", this);
        }
      }
    }

    @Override
    public void cleanUp() {
      if (this.filterProfile != null) {
        this.filterProfile.cleanUp();
      }
    }

    /**
     * Attempts to process this message with the specified <code>CacheDistributionAdvisee</code>.
     * 
     * @param cda the CacheDistributionAdvisee to apply this profile to
     * @param isRealRegion true if CacheDistributionAdvisee is a real region
     */
    private void handleCacheDistributionAdvisee(CacheDistributionAdvisee cda, String adviseePath,
        boolean removeProfile, boolean exchangeProfiles, boolean isRealRegion,
        final List<Profile> replyProfiles) {
      if (cda != null) {
        handleDistributionAdvisee(cda, removeProfile, isRealRegion && exchangeProfiles,
            replyProfiles);
        if (logger.isDebugEnabled()) {
          logger.debug("While processing UpdateAttributes message, handled advisee: {}", cda);
        }
      } else {
        if (logger.isDebugEnabled()) {
          logger.debug("While processing UpdateAttributes message, region not found: {}",
              adviseePath);
        }
      }
    }

    @Override
    public int getDSFID() {
      return CACHE_PROFILE;
    }

    @Override
    public void toData(DataOutput out) throws IOException {
      super.toData(out);
      out.writeInt(getIntInfo());
      if (persistentID != null) {
        InternalDataSerializer.invokeToData(persistentID, out);
      }
      if (!gatewaySenderIds.isEmpty()) {
        writeSet(gatewaySenderIds, out);
      }
      if (!asyncEventQueueIds.isEmpty()) {
        writeSet(asyncEventQueueIds, out);
      }
      DataSerializer.writeObject(this.filterProfile, out);
      if (!cacheServiceProfiles.isEmpty()) {
        DataSerializer.writeObject(cacheServiceProfiles, out);
      }
    }

    private void writeSet(Set<?> set, DataOutput out) throws IOException {
      // to fix bug 47205 always serialize the Set as a HashSet.
      out.writeByte(DSCODE.HASH_SET);
      InternalDataSerializer.writeSet(set, out);
    }

    @Override
    public void fromData(DataInput in) throws IOException, ClassNotFoundException {
      super.fromData(in);
      int bits = in.readInt();
      setIntInfo(bits);
      if (hasPersistentID(bits)) {
        persistentID = new PersistentMemberID();
        InternalDataSerializer.invokeFromData(persistentID, in);
      }
      if (hasGatewaySenderIds(bits)) {
        gatewaySenderIds = DataSerializer.readObject(in);
      }
      if (hasAsyncEventQueueIds(bits)) {
        asyncEventQueueIds = DataSerializer.readObject(in);
      }
      this.filterProfile = DataSerializer.readObject(in);
      if (hasCacheServiceProfiles(bits)) {
        cacheServiceProfiles = DataSerializer.readObject(in);
      }
    }

    @Override
    public StringBuilder getToStringHeader() {
      return new StringBuilder("CacheProfile");
    }

    @Override
    public void fillInToString(StringBuilder sb) {
      super.fillInToString(sb);
      sb.append("; dataPolicy=" + this.dataPolicy);
      sb.append("; hasCacheLoader=" + this.hasCacheLoader);
      sb.append("; hasCacheWriter=" + this.hasCacheWriter);
      sb.append("; hasCacheListener=" + this.hasCacheListener);
      sb.append("; hasCacheServer=").append(this.hasCacheServer);
      sb.append("; scope=" + this.scope);
      sb.append("; regionInitialized=").append(String.valueOf(this.regionInitialized));
      sb.append("; inRecovery=" + this.inRecovery);
      sb.append("; subcription=" + this.subscriptionAttributes);
      sb.append("; isPartitioned=" + this.isPartitioned);
      sb.append("; isGatewayEnabled=" + this.isGatewayEnabled);
      sb.append("; isPersistent=" + this.isPersistent);
      sb.append("; persistentID=" + this.persistentID);
      if (this.filterProfile != null) {
        sb.append("; ").append(this.filterProfile);
      }
      sb.append("; gatewaySenderIds =" + this.gatewaySenderIds);
      sb.append("; asyncEventQueueIds =" + this.asyncEventQueueIds);
      sb.append("; IsOffHeap=" + this.isOffHeap);
      sb.append("; cacheServiceProfiles=" + this.cacheServiceProfiles);
    }
  }

  /** Recipient information used for getInitialImage operation */
  public static class InitialImageAdvice {
    public Set<InternalDistributedMember> getOthers() {
      return this.others;
    }

    public void setOthers(Set<InternalDistributedMember> others) {
      this.others = others;
    }

    public Set<InternalDistributedMember> getReplicates() {
      return this.replicates;
    }

    public Set<InternalDistributedMember> getNonPersistent() {
      return this.nonPersistent;
    }

    public Set<InternalDistributedMember> getPreloaded() {
      return this.preloaded;
    }

    public Set<InternalDistributedMember> getEmpties() {
      return this.empties;
    }

    public Set<InternalDistributedMember> getUninitialized() {
      return this.uninitialized;
    }

    /** Set of replicate recipients */
    protected final Set<InternalDistributedMember> replicates;

    /** Set of peers that are preloaded */
    protected final Set<InternalDistributedMember> preloaded;

    /**
     * Set of tertiary recipients which are not replicates, in which case they should all be queried
     * and a superset taken of their images. To be used only if the image cannot be obtained from
     * the replicates set.
     */
    protected Set<InternalDistributedMember> others;

    /** Set of members that might be data feeds and have EMPTY data policy */
    protected final Set<InternalDistributedMember> empties;

    /** Set of members that may not have finished initializing their caches */
    protected final Set<InternalDistributedMember> uninitialized;

    /** Set of members that are replicates but not persistent */
    protected final Set<InternalDistributedMember> nonPersistent;

    private final Map<InternalDistributedMember, CacheProfile> memberProfiles;


    protected InitialImageAdvice(Set<InternalDistributedMember> replicates,
        Set<InternalDistributedMember> others, Set<InternalDistributedMember> preloaded,
        Set<InternalDistributedMember> empties, Set<InternalDistributedMember> uninitialized,
        Set<InternalDistributedMember> nonPersistent,
        Map<InternalDistributedMember, CacheProfile> memberProfiles) {
      this.replicates = replicates;
      this.others = others;
      this.preloaded = preloaded;
      this.empties = empties;
      this.uninitialized = uninitialized;
      this.nonPersistent = nonPersistent;
      this.memberProfiles = memberProfiles;
    }

    public InitialImageAdvice() {
      this(Collections.EMPTY_SET, Collections.EMPTY_SET, Collections.EMPTY_SET,
          Collections.EMPTY_SET, Collections.EMPTY_SET, Collections.EMPTY_SET,
          Collections.<InternalDistributedMember, CacheProfile>emptyMap());
    }

    @Override
    public String toString() {
      return "InitialImageAdvice(" + "replicates=" + this.replicates + "; others=" + this.others
          + "; preloaded=" + this.preloaded + "; empty=" + this.empties + "; initializing="
          + this.uninitialized + ")";
    }

  }

  // moved putProfile, doPutProfile, and putProfile to DistributionAdvisor

  // moved isNewerProfile to DistributionAdvisor

  // moved isNewerSerialNumber to DistributionAdvisor

  // moved forceNewMembershipVersion to DistributionAdvisor

  // moved startOperation to DistributionAdvisor

  // moved endOperation to DistributionAdvisor

  /**
   * Provide only the new replicates given a set of existing memberIds
   * 
   * @param oldRecipients the <code>Set</code> of memberIds that have received the message
   * @return the set of new replicate's memberIds
   * @since GemFire 5.1
   */
  public Set adviseNewReplicates(final Set oldRecipients) {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        if (cp.dataPolicy.withReplication() && !oldRecipients.contains(cp.getDistributedMember())) {
          return true;
        }
        return false;
      }
    });
  }

  // moved waitForCurrentOperations to DistributionAdvisor

  // moved removeId, doRemoveId, removeIdWithSerial, and updateRemovedProfiles to
  // DistributionAdvisor

  /**
   * Provide all the replicates including persistent replicates.
   * 
   * @return the set of replicate's memberIds
   * @since GemFire 5.8
   */
  public Set<InternalDistributedMember> adviseReplicates() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        if (cp.dataPolicy.withReplication()) {
          return true;
        }
        return false;
      }
    });
  }

  /**
   * Provide only the preloadeds given a set of existing memberIds
   * 
   * @return the set of preloaded's memberIds
   * @since GemFire prPersistSprint1
   */
  public Set advisePreloadeds() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        if (cp.dataPolicy.withPreloaded()) {
          return true;
        }
        return false;
      }
    });
  }

  /**
   * Provide only the empty's (having DataPolicy.EMPTY) given a set of existing memberIds
   * 
   * @return the set of replicate's memberIds
   * @since GemFire 5.8
   */
  public Set adviseEmptys() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        if (cp.dataPolicy.isEmpty()) {
          return true;
        }
        return false;
      }
    });
  }

  /**
   * Provide only the normals (having DataPolicy.NORMAL) given a set of existing memberIds
   * 
   * @return the set of normal's memberIds
   * @since GemFire 5.8
   */
  public Set adviseNormals() {
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        if (cp.dataPolicy.isNormal()) {
          return true;
        }
        return false;
      }
    });
  }

  @Override
  protected void profileRemoved(Profile profile) {
    if (logger.isDebugEnabled()) {
      logger.debug("CDA: removing profile {}", profile);
    }
    if (getAdvisee() instanceof LocalRegion && profile != null) {
      ((LocalRegion) getAdvisee()).removeMemberFromCriticalList(profile.getDistributedMember());
    }
  }

  /**
   * Returns the list of all persistent members. For most cases, adviseInitializedPersistentMembers
   * is more appropriate. These method includes members that are still in the process of GII.
   */
  public Map<InternalDistributedMember, PersistentMemberID> advisePersistentMembers() {
    initializationGate();

    Map<InternalDistributedMember, PersistentMemberID> result =
        new HashMap<InternalDistributedMember, PersistentMemberID>();
    Profile[] snapshot = this.profiles;
    for (Profile profile : snapshot) {
      CacheProfile cp = (CacheProfile) profile;
      if (cp.persistentID != null) {
        result.put(cp.getDistributedMember(), cp.persistentID);
      }
    }

    return result;
  }

  public Map<InternalDistributedMember, PersistentMemberID> adviseInitializedPersistentMembers() {
    initializationGate();

    Map<InternalDistributedMember, PersistentMemberID> result =
        new HashMap<InternalDistributedMember, PersistentMemberID>();
    Profile[] snapshot = this.profiles;
    for (Profile profile : snapshot) {
      CacheProfile cp = (CacheProfile) profile;
      if (cp.persistentID != null && cp.persistenceInitialized) {
        result.put(cp.getDistributedMember(), cp.persistentID);
      }
    }

    return result;
  }

  public Set adviseCacheServers() {
    getAdvisee().getCancelCriterion().checkCancelInProgress(null);
    return adviseFilter(new Filter() {
      public boolean include(Profile profile) {
        assert profile instanceof CacheProfile;
        CacheProfile cp = (CacheProfile) profile;
        return cp.hasCacheServer;
      }
    });
  }

  // Overrided for bucket regions. This listener also receives events
  // about PR joins and leaves.
  public void addMembershipAndProxyListener(MembershipListener listener) {
    addMembershipListener(listener);
  }

  public void removeMembershipAndProxyListener(MembershipListener listener) {
    removeMembershipListener(listener);
  }

  @Override
  public boolean removeId(ProfileId memberId, boolean crashed, boolean destroyed,
      boolean fromMembershipListener) {

    boolean isPersistent = false;
    DiskStoreID persistentId = null;
    CacheDistributionAdvisee advisee = (CacheDistributionAdvisee) getAdvisee();
    if (advisee.getAttributes().getDataPolicy().withPersistence()) {
      isPersistent = true;
      CacheProfile profile = (CacheProfile) getProfile(memberId);
      if (profile != null && profile.persistentID != null) {
        persistentId = ((CacheProfile) getProfile(memberId)).persistentID.diskStoreId;
      }
    }

    boolean result = super.removeId(memberId, crashed, destroyed, fromMembershipListener);

    // bug #48962 - record members that leave during GII so IIOp knows about them
    if (advisee instanceof DistributedRegion) {
      DistributedRegion r = (DistributedRegion) advisee;
      if (!r.isInitialized() && !r.isUsedForPartitionedRegionBucket()) {
        if (logger.isDebugEnabled()) {
          logger.debug("recording that {} has left during initialization of {}", memberId,
              r.getName());
        }
        ImageState state = r.getImageState();
        if (isPersistent) {
          if (persistentId != null) {
            state.addLeftMember(persistentId);
          }
        } else {
          state.addLeftMember((InternalDistributedMember) memberId);
        }
      }
    }
    return result;
  }

  public List<Set<String>> adviseSameGatewaySenderIds(final Set<String> allGatewaySenderIds) {
    final List<Set<String>> differSenderIds = new ArrayList<Set<String>>();
    fetchProfiles(new Filter() {
      public boolean include(final Profile profile) {
        if (profile instanceof CacheProfile) {
          final CacheProfile cp = (CacheProfile) profile;
          if (allGatewaySenderIds.equals(cp.gatewaySenderIds)) {
            return true;
          } else {
            differSenderIds.add(allGatewaySenderIds);
            differSenderIds.add(cp.gatewaySenderIds);
            return false;
          }
        }
        return false;
      }
    });
    return differSenderIds;
  }

  public List<Set<String>> adviseSameAsyncEventQueueIds(final Set<String> allAsyncEventIds) {
    final List<Set<String>> differAsycnQueueIds = new ArrayList<Set<String>>();
    List l = fetchProfiles(new Filter() {
      public boolean include(final Profile profile) {
        if (profile instanceof CacheProfile) {
          final CacheProfile cp = (CacheProfile) profile;
          if (allAsyncEventIds.equals(cp.asyncEventQueueIds)) {
            return true;
          } else {
            differAsycnQueueIds.add(allAsyncEventIds);
            differAsycnQueueIds.add(cp.asyncEventQueueIds);
            return false;
          }
        }
        return false;
      }
    });
    return differAsycnQueueIds;
  }

}
