/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.master.file.meta; import alluxio.Constants; import alluxio.exception.ExceptionMessage; import alluxio.exception.InvalidPathException; import alluxio.master.journal.JournalEntryRepresentable; import alluxio.wire.FileInfo; import alluxio.wire.TtlAction; import com.google.common.base.Objects; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.annotation.concurrent.NotThreadSafe; /** * {@link Inode} is an abstract class, with information shared by all types of Inodes. The inode * must be locked ({@link #lockRead()} or {@link #lockWrite()}) before methods are called. * * @param <T> the concrete subclass of this object */ @NotThreadSafe public abstract class Inode<T> implements JournalEntryRepresentable { protected long mCreationTimeMs; private boolean mDeleted; protected final boolean mDirectory; protected final long mId; protected long mTtl; protected TtlAction mTtlAction; private long mLastModificationTimeMs; private String mName; private long mParentId; private PersistenceState mPersistenceState; private boolean mPinned; private String mOwner; private String mGroup; private short mMode; private final ReentrantReadWriteLock mLock; protected Inode(long id, boolean isDirectory) { mCreationTimeMs = System.currentTimeMillis(); mDeleted = false; mDirectory = isDirectory; mGroup = ""; mId = id; mTtl = Constants.NO_TTL; mTtlAction = TtlAction.DELETE; mLastModificationTimeMs = mCreationTimeMs; mName = null; mParentId = InodeTree.NO_PARENT; mMode = Constants.INVALID_MODE; mPersistenceState = PersistenceState.NOT_PERSISTED; mPinned = false; mOwner = ""; mLock = new ReentrantReadWriteLock(); } /** * @return the create time, in milliseconds */ public long getCreationTimeMs() { return mCreationTimeMs; } /** * @return the group of the inode */ public String getGroup() { return mGroup; } /** * @return the id of the inode */ public long getId() { return mId; } /** * @return the ttl of the file */ public long getTtl() { return mTtl; } /** * @return the {@link TtlAction} */ public TtlAction getTtlAction() { return mTtlAction; } /** * @return the last modification time, in milliseconds */ public long getLastModificationTimeMs() { return mLastModificationTimeMs; } /** * @return the name of the inode */ public String getName() { return mName; } /** * @return the mode of the inode */ public short getMode() { return mMode; } /** * @return the {@link PersistenceState} of the inode */ public PersistenceState getPersistenceState() { return mPersistenceState; } /** * Compare-and-swaps the persistence state. * * @param oldState the old {@link PersistenceState} * @param newState the new {@link PersistenceState} to set * @return true if the old state matches and the new state was set */ public boolean compareAndSwap(PersistenceState oldState, PersistenceState newState) { synchronized (this) { if (mPersistenceState == oldState) { mPersistenceState = newState; return true; } return false; } } /** * @return the id of the parent folder */ public long getParentId() { return mParentId; } /** * @return the owner of the inode */ public String getOwner() { return mOwner; } /** * @return true if the inode is deleted, false otherwise */ public boolean isDeleted() { return mDeleted; } /** * @return true if the inode is a directory, false otherwise */ public boolean isDirectory() { return mDirectory; } /** * @return true if the inode is a file, false otherwise */ public boolean isFile() { return !mDirectory; } /** * @return true if the inode is pinned, false otherwise */ public boolean isPinned() { return mPinned; } /** * @return true if the file has persisted, false otherwise */ public boolean isPersisted() { return mPersistenceState == PersistenceState.PERSISTED; } /** * @param creationTimeMs the creation time to use (in milliseconds) * @return the updated object */ public T setCreationTimeMs(long creationTimeMs) { mCreationTimeMs = creationTimeMs; return getThis(); } /** * @param deleted the deleted flag to use * @return the updated object */ public T setDeleted(boolean deleted) { mDeleted = deleted; return getThis(); } /** * @param group the group of the inode * @return the updated object */ public T setGroup(String group) { mGroup = group; return getThis(); } /** * Sets the last modification time of the inode to the new time if the new time is more recent. * This method can be called concurrently with deterministic results. * * @param lastModificationTimeMs the last modification time to use * @return the updated object */ public T setLastModificationTimeMs(long lastModificationTimeMs) { return setLastModificationTimeMs(lastModificationTimeMs, false); } /** * @param lastModificationTimeMs the last modification time to use * @param override if true, sets the value regardless of the previous last modified time, * should be set to true for journal replay * @return the updated object */ public T setLastModificationTimeMs(long lastModificationTimeMs, boolean override) { synchronized (this) { if (override || mLastModificationTimeMs < lastModificationTimeMs) { mLastModificationTimeMs = lastModificationTimeMs; } return getThis(); } } /** * @param name the name to use * @return the updated object */ public T setName(String name) { mName = name; return getThis(); } /** * @param parentId the parent id to use * @return the updated object */ public T setParentId(long parentId) { mParentId = parentId; return getThis(); } /** * @param ttl the TTL to use, in milliseconds * @return the updated object */ public T setTtl(long ttl) { mTtl = ttl; return getThis(); } /** * @param ttlAction the {@link TtlAction} to use * @return the updated options object */ public T setTtlAction(TtlAction ttlAction) { mTtlAction = ttlAction; return getThis(); } /** * @param persistenceState the {@link PersistenceState} to use * @return the updated object */ public T setPersistenceState(PersistenceState persistenceState) { mPersistenceState = persistenceState; return getThis(); } /** * @param pinned the pinned flag value to use * @return the updated object */ public T setPinned(boolean pinned) { mPinned = pinned; return getThis(); } /** * @param owner the owner name of the inode * @return the updated object */ public T setOwner(String owner) { mOwner = owner; return getThis(); } /** * @param mode the mode of the inode * @return the updated object */ public T setMode(short mode) { mMode = mode; return getThis(); } /** * Generates a {@link FileInfo} of the file or folder. * * @param path the path of the file * @return generated {@link FileInfo} */ public abstract FileInfo generateClientFileInfo(String path); /** * @return {@code this} so that the abstract class can use the fluent builder pattern */ protected abstract T getThis(); /** * Obtains a read lock on the inode. This call should only be used when locking the root or an * inode by id and not path or parent. */ public void lockRead() { mLock.readLock().lock(); } /** * Obtains a read lock on the inode. Afterward, checks the inode state: * - parent is consistent with what the caller is expecting * - the inode is not marked as deleted * If the state is inconsistent, an exception will be thrown and the lock will be released. * * NOTE: This method assumes that the inode path to the parent has been read locked. * * @param parent the expected parent inode * @throws InvalidPathException if the parent is not as expected */ public void lockReadAndCheckParent(Inode parent) throws InvalidPathException { lockRead(); if (mDeleted) { unlockRead(); throw new InvalidPathException(ExceptionMessage.PATH_INVALID_CONCURRENT_DELETE.getMessage()); } if (mParentId != InodeTree.NO_PARENT && mParentId != parent.getId()) { unlockRead(); throw new InvalidPathException(ExceptionMessage.PATH_INVALID_CONCURRENT_RENAME.getMessage()); } } /** * Obtains a read lock on the inode. Afterward, checks the inode state to ensure the full inode * path is consistent with what the caller is expecting. If the state is inconsistent, an * exception will be thrown and the lock will be released. * * NOTE: This method assumes that the inode path to the parent has been read locked. * * @param parent the expected parent inode * @param name the expected name of the inode to be locked * @throws InvalidPathException if the parent and/or name is not as expected */ public void lockReadAndCheckNameAndParent(Inode parent, String name) throws InvalidPathException { lockReadAndCheckParent(parent); if (!mName.equals(name)) { unlockRead(); throw new InvalidPathException(ExceptionMessage.PATH_INVALID_CONCURRENT_RENAME.getMessage()); } } /** * Obtains a write lock on the inode. This call should only be used when locking the root or an * inode by id and not path or parent. */ public void lockWrite() { mLock.writeLock().lock(); } /** * Obtains a write lock on the inode. Afterward, checks the inode state: * - parent is consistent with what the caller is expecting * - the inode is not marked as deleted * If the state is inconsistent, an exception will be thrown and the lock will be released. * * NOTE: This method assumes that the inode path to the parent has been read locked. * * @param parent the expected parent inode * @throws InvalidPathException if the parent is not as expected */ public void lockWriteAndCheckParent(Inode parent) throws InvalidPathException { lockWrite(); if (mDeleted) { unlockWrite(); throw new InvalidPathException(ExceptionMessage.PATH_INVALID_CONCURRENT_DELETE.getMessage()); } if (mParentId != InodeTree.NO_PARENT && mParentId != parent.getId()) { unlockWrite(); throw new InvalidPathException(ExceptionMessage.PATH_INVALID_CONCURRENT_RENAME.getMessage()); } } /** * Obtains a write lock on the inode. Afterward, checks the inode state to ensure the full inode * path is consistent with what the caller is expecting. If the state is inconsistent, an * exception will be thrown and the lock will be released. * * NOTE: This method assumes that the inode path to the parent has been read locked. * * @param parent the expected parent inode * @param name the expected name of the inode to be locked * @throws InvalidPathException if the parent and/or name is not as expected */ public void lockWriteAndCheckNameAndParent(Inode parent, String name) throws InvalidPathException { lockWriteAndCheckParent(parent); if (!mName.equals(name)) { unlockWrite(); throw new InvalidPathException(ExceptionMessage.PATH_INVALID_CONCURRENT_RENAME.getMessage()); } } /** * Releases the read lock for this inode. */ public void unlockRead() { mLock.readLock().unlock(); } /** * Releases the write lock for this inode. */ public void unlockWrite() { mLock.writeLock().unlock(); } /** * @return returns true if the current thread holds a write lock on this inode, false otherwise */ public boolean isWriteLocked() { return mLock.isWriteLockedByCurrentThread(); } /** * @return returns true if the current thread holds a read lock on this inode, false otherwise */ public boolean isReadLocked() { return mLock.getReadHoldCount() > 0; } @Override public int hashCode() { return ((Long) mId).hashCode(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Inode<?>)) { return false; } Inode<?> that = (Inode<?>) o; return mId == that.mId; } protected Objects.ToStringHelper toStringHelper() { return Objects.toStringHelper(this).add("id", mId).add("name", mName).add("parentId", mParentId) .add("creationTimeMs", mCreationTimeMs).add("pinned", mPinned).add("deleted", mDeleted) .add("ttl", mTtl).add("ttlAction", mTtlAction) .add("directory", mDirectory).add("persistenceState", mPersistenceState) .add("lastModificationTimeMs", mLastModificationTimeMs).add("owner", mOwner) .add("group", mGroup).add("permission", mMode); } }