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

import org.apache.geode.SerializationException;
import org.apache.geode.cache.operations.GetOperationContext;
import org.apache.geode.internal.offheap.Releasable;
import org.apache.geode.internal.offheap.StoredObject;
import org.apache.geode.internal.offheap.annotations.Unretained;

/**
 * This subclass's job is to keep customers from getting a reference to a value that is off-heap.
 * Any access to an off-heap value should appear to the customer as a serialized value.
 *
 * @deprecated since Geode1.0, use {@link org.apache.geode.security.ResourcePermission} instead
 */
public class GetOperationContextImpl extends GetOperationContext implements Releasable {

  private boolean released;

  public GetOperationContextImpl(Object key, boolean postOperation) {
    super(key, postOperation);
  }

  /**
   * This method is for internal use and should not be on the public apis.
   */
  public @Unretained Object getRawValue() {
    return super.getValue();
  }

  @Override
  public Object getObject() {
    Object result = super.getObject();
    if (result instanceof StoredObject) {
      // For off-heap object act as if they are serialized forcing them to call getSerializedValue
      // or getValue
      result = null;
    }
    return result;
  }

  @Override
  public void setObject(Object value, boolean isObject) {
    this.released = false;
    super.setObject(value, isObject);
  }

  @Override
  public void setValue(Object value, boolean isObject) {
    this.released = false;
    super.setValue(value, isObject);
  }

  private void checkForReleasedOffHeapValue(StoredObject so) {
    // Note that we only care about stored objects with a ref count
    if (this.released && so.hasRefCount()) {
      throw new IllegalStateException(
          "Attempt to access off-heap value after the OperationContext callback returned.");
    }
  }

  @Override
  public byte[] getSerializedValue() {
    byte[] result = super.getSerializedValue();
    if (result == null) {
      Object v = super.getValue();
      if (v instanceof StoredObject) {
        StoredObject so = (StoredObject) v;
        checkForReleasedOffHeapValue(so);
        result = so.getValueAsHeapByteArray();
      }
    }
    return result;
  }

  @Override
  public Object getDeserializedValue() throws SerializationException {
    Object result = super.getDeserializedValue();
    if (result instanceof StoredObject) {
      StoredObject so = (StoredObject) result;
      checkForReleasedOffHeapValue(so);
      result = so.getValueAsDeserializedHeapObject();
    }
    return result;
  }

  @Override
  public Object getValue() {
    Object result = super.getValue();
    if (result instanceof StoredObject) {
      StoredObject so = (StoredObject) result;
      checkForReleasedOffHeapValue(so);
      // since they called getValue they don't care if it is serialized or deserialized so return it
      // as serialized
      result = so.getValueAsHeapByteArray();
    }
    return result;
  }

  @Override
  public void release() {
    // Note that if the context's value is stored off-heap
    // and release has been called then we do not release
    // our value (since this context did not retain it)
    // but we do make sure that any future attempt to access
    // the off-heap value fails.
    if (super.getValue() instanceof StoredObject) {
      this.released = true;
    }
  }

}
