/*
* Copyright 2009-2014 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
*/
package com.eucalyptus.objectstorage.metadata;
import java.util.NoSuchElementException;
import javax.annotation.Nullable;
import org.apache.log4j.Logger;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.objectstorage.BucketState;
import com.eucalyptus.objectstorage.MpuPartMetadataManagers;
import com.eucalyptus.objectstorage.ObjectMetadataManagers;
import com.eucalyptus.objectstorage.ObjectState;
import com.eucalyptus.objectstorage.entities.Bucket;
import com.eucalyptus.objectstorage.entities.ObjectEntity;
import com.eucalyptus.objectstorage.exceptions.IllegalResourceStateException;
import com.eucalyptus.objectstorage.exceptions.MetadataOperationFailureException;
import com.eucalyptus.objectstorage.exceptions.NoSuchEntityException;
import com.eucalyptus.objectstorage.exceptions.ObjectStorageInternalException;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
/**
* Created by zhill on 2/14/14.
*/
public class ObjectStateTransitions {
private static final Logger LOG = Logger.getLogger(ObjectStateTransitions.class);
/**
* Inserts the new record into the db in 'creating' state.
*/
static final Function<ObjectEntity, ObjectEntity> TRANSITION_TO_CREATING = new Function<ObjectEntity, ObjectEntity>() {
@Nullable
@Override
public ObjectEntity apply(@Nullable ObjectEntity initializedObject) {
if (initializedObject == null) {
throw new RuntimeException("Null bucket record cannot be updated");
} else {
Bucket extantBucket;
try {
extantBucket = Entities.uniqueResult(new Bucket().withUuid(initializedObject.getBucket().getBucketUuid()));
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(e);
} catch (TransactionException e) {
throw new MetadataOperationFailureException(e);
}
if (extantBucket == null) {
throw new NoSuchEntityException(initializedObject.getBucket().getBucketUuid());
}
if (initializedObject.getState() != null && !ObjectState.creating.equals(initializedObject.getState())) {
throw new IllegalResourceStateException(initializedObject.getResourceFullName(), null, ObjectState.creating.toString(), initializedObject
.getState().toString());
} else {
if (extantBucket == null || !BucketState.extant.equals(extantBucket.getState())) {
// invalid bucket, cannot create
throw new IllegalResourceStateException(extantBucket.getBucketUuid(), null, BucketState.extant.toString(), extantBucket.getState()
.toString());
}
initializedObject.setBucket(extantBucket);
initializedObject.setState(ObjectState.creating);
initializedObject.updateCreationExpiration();
return Entities.persist(initializedObject);
}
}
}
};
/**
* Function that does the actual update of state. Will only transition creating->extant or mpu_pending->extant
*/
static final Function<ObjectEntity, ObjectEntity> TRANSITION_TO_EXTANT = new Function<ObjectEntity, ObjectEntity>() {
@Nullable
@Override
public ObjectEntity apply(@Nullable ObjectEntity entity) {
if (entity == null) {
throw new RuntimeException("Null bucket record cannot be updated");
} else {
try {
ObjectEntity updatingEntity = Entities.uniqueResult(new ObjectEntity().withUuid(entity.getObjectUuid()));
if (!ObjectState.deleting.equals(entity.getState())) {
// Remove old versions and update bucket size within this transaction.
// If quota enforcement wasn't needed this would be unnecessary.
// NOTE: this must be done before the new object is updated or it will clean it as well due to Hibernate context
// and session. First remove nulls, then set state to extant.
/*
* ObjectMetadataManagers.getInstance().cleanupAllNullVersionedObjectRecords(entity.getBucket(), entity.getObjectKey());
*
* if(ObjectState.mpu_pending.equals(entity.getState())) { MpuPartMetadataManagers.getInstance().removeParts(entity.getBucket(),
* entity.getUploadId()); }
*/
// Set the new object state
updatingEntity.setState(ObjectState.extant);
updatingEntity.setCreationExpiration(null);
updatingEntity.setObjectModifiedTimestamp(entity.getObjectModifiedTimestamp());
updatingEntity.setIsLatest(true);
updatingEntity.seteTag(entity.geteTag());
updatingEntity.setSize(entity.getSize());
updatingEntity.setStoredHeaders(entity.getStoredHeaders());
if (ObjectState.mpu_pending.equals(updatingEntity.getLastState())) {
// Remove the parts, this will remove the sizes for the parts.
MpuPartMetadataManagers.getInstance().removeParts(updatingEntity.getBucket(), updatingEntity.getUploadId());
}
ObjectMetadataManagers.getInstance().cleanupInvalidObjects(updatingEntity.getBucket(), updatingEntity.getObjectKey());
} else {
throw new IllegalResourceStateException("Cannot transition to extant from non-creating state", null, ObjectState.creating.toString(),
entity.getState().toString());
}
return updatingEntity;
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(entity.getObjectUuid());
} catch (ObjectStorageInternalException e) {
throw e;
} catch (Exception e) {
LOG.warn("Cannot update state of object " + entity.getObjectUuid(), e);
throw new MetadataOperationFailureException(e);
}
}
}
};
/**
* Function that does the actual update of state. Will only transition creating->mpu_pending
*/
static final Function<ObjectEntity, ObjectEntity> TRANSITION_TO_MPU_PENDING = new Function<ObjectEntity, ObjectEntity>() {
@Nullable
@Override
public ObjectEntity apply(@Nullable ObjectEntity entity) {
if (entity == null) {
throw new RuntimeException("Null bucket record cannot be updated");
} else {
try {
ObjectEntity updatingEntity = Entities.uniqueResult(new ObjectEntity().withUuid(entity.getObjectUuid()));
if (ObjectState.creating.equals(updatingEntity.getState())) {
updatingEntity.setState(ObjectState.mpu_pending);
updatingEntity.setCreationExpiration(null);
updatingEntity.setObjectModifiedTimestamp(entity.getObjectModifiedTimestamp());
updatingEntity.setUploadId(entity.getUploadId());
} else {
throw new IllegalResourceStateException("Cannot transition to mpu-pending from non-creating state", null,
ObjectState.creating.toString(), entity.getState().toString());
}
return updatingEntity;
} catch (ObjectStorageInternalException e) {
throw e;
} catch (Exception e) {
LOG.warn("Cannot update state of object " + entity.getObjectUuid(), e);
throw new MetadataOperationFailureException(e);
}
}
}
};
/**
* Function that does the actual update of state. Will transition from any state -> deleting
*/
static final Function<ObjectEntity, ObjectEntity> TRANSITION_TO_DELETING = new Function<ObjectEntity, ObjectEntity>() {
@Nullable
@Override
public ObjectEntity apply(@Nullable ObjectEntity objectToUpdate) {
if (objectToUpdate == null) {
throw new RuntimeException("Null bucket record cannot be updated");
} else {
try {
ObjectEntity entity;
// Shortcut to avoid a lookup if we're already loaded in a transaction
// This is most common case for deletion operations
if (!Entities.isPersistent(objectToUpdate)) {
entity = Entities.uniqueResult(new ObjectEntity().withUuid(objectToUpdate.getObjectUuid()));
} else {
entity = objectToUpdate;
}
entity.setState(ObjectState.deleting);
entity.setIsLatest(Boolean.FALSE);
return entity;
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(objectToUpdate.getObjectUuid());
} catch (Exception e) {
throw new MetadataOperationFailureException(e);
}
}
}
};
/**
* Does the actual deletion of the entity
*/
static final Predicate<ObjectEntity> TRANSITION_TO_DELETED = new Predicate<ObjectEntity>() {
@Nullable
@Override
public boolean apply(@Nullable ObjectEntity objectToUpdate) {
if (objectToUpdate == null) {
throw new RuntimeException("Null bucket record cannot be updated");
} else {
try {
ObjectEntity entity;
// Shortcut to avoid a lookup if we're already loaded in a transaction
// This is most common case for deletion operations
if (!Entities.isPersistent(objectToUpdate)) {
entity = Entities.uniqueResult(new ObjectEntity().withUuid(objectToUpdate.getObjectUuid()));
} else {
entity = objectToUpdate;
}
if (!ObjectState.deleting.equals(objectToUpdate.getState())) {
throw new IllegalResourceStateException("Entity not in deleting state. Only valid transition to deleted is from deleting.", null,
ObjectState.deleting.toString(), objectToUpdate.getState().toString());
}
Entities.delete(entity);
return true;
} catch (ObjectStorageInternalException e) {
throw e;
} catch (NoSuchElementException e) {
throw new NoSuchEntityException(objectToUpdate.getObjectUuid());
} catch (Exception e) {
throw new MetadataOperationFailureException(e);
}
}
}
};
}