| /* |
| * 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.ignite.internal.processors.igfs; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.IgniteException; |
| import org.apache.ignite.IgniteSystemProperties; |
| import org.apache.ignite.binary.BinaryRawReader; |
| import org.apache.ignite.binary.BinaryRawWriter; |
| import org.apache.ignite.cache.CacheMode; |
| import org.apache.ignite.cache.CacheWriteSynchronizationMode; |
| import org.apache.ignite.cluster.ClusterNode; |
| import org.apache.ignite.cluster.ClusterTopologyException; |
| import org.apache.ignite.configuration.CacheConfiguration; |
| import org.apache.ignite.configuration.FileSystemConfiguration; |
| import org.apache.ignite.configuration.IgniteConfiguration; |
| import org.apache.ignite.events.IgfsEvent; |
| import org.apache.ignite.igfs.IgfsException; |
| import org.apache.ignite.igfs.IgfsGroupDataBlocksKeyMapper; |
| import org.apache.ignite.igfs.IgfsIpcEndpointConfiguration; |
| import org.apache.ignite.igfs.IgfsMode; |
| import org.apache.ignite.igfs.IgfsPath; |
| import org.apache.ignite.internal.GridKernalContext; |
| import org.apache.ignite.internal.binary.BinaryUtils; |
| import org.apache.ignite.internal.cluster.ClusterTopologyServerNotFoundException; |
| import org.apache.ignite.internal.managers.eventstorage.GridEventStorageManager; |
| import org.apache.ignite.internal.processors.cache.IgniteInternalCache; |
| import org.apache.ignite.internal.processors.query.QueryUtils; |
| import org.apache.ignite.internal.util.IgniteUtils; |
| import org.apache.ignite.internal.util.future.IgniteFutureImpl; |
| import org.apache.ignite.internal.util.lang.IgniteOutClosureX; |
| import org.apache.ignite.internal.util.typedef.F; |
| import org.apache.ignite.internal.util.typedef.T2; |
| import org.apache.ignite.internal.util.typedef.X; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.lang.IgniteUuid; |
| import org.apache.ignite.transactions.Transaction; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.lang.reflect.Constructor; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.ThreadLocalRandom; |
| |
| import static org.apache.ignite.IgniteSystemProperties.IGNITE_CACHE_RETRIES_COUNT; |
| import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL; |
| import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; |
| import static org.apache.ignite.igfs.IgfsMode.DUAL_ASYNC; |
| import static org.apache.ignite.igfs.IgfsMode.DUAL_SYNC; |
| import static org.apache.ignite.igfs.IgfsMode.PRIMARY; |
| import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_IGFS; |
| import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC; |
| import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ; |
| |
| /** |
| * Common IGFS utility methods. |
| */ |
| public class IgfsUtils { |
| /** ID for the root directory. */ |
| public static final IgniteUuid ROOT_ID = new IgniteUuid(new UUID(0, 0), 0); |
| |
| /** Lock Id used to lock files being deleted from TRASH. This is a global constant. */ |
| public static final IgniteUuid DELETE_LOCK_ID = new IgniteUuid(new UUID(0, 0), 0); |
| |
| /** Constant trash concurrency level. */ |
| public static final int TRASH_CONCURRENCY = 64; |
| |
| /** File property: user name. */ |
| public static final String PROP_USER_NAME = "usrName"; |
| |
| /** File property: group name. */ |
| public static final String PROP_GROUP_NAME = "grpName"; |
| |
| /** File property: permission. */ |
| public static final String PROP_PERMISSION = "permission"; |
| |
| /** File property: prefer writes to local node. */ |
| public static final String PROP_PREFER_LOCAL_WRITES = "locWrite"; |
| |
| /** Generic property index. */ |
| private static final byte PROP_IDX = 0; |
| |
| /** User name property index. */ |
| private static final byte PROP_USER_NAME_IDX = 1; |
| |
| /** Group name property index. */ |
| private static final byte PROP_GROUP_NAME_IDX = 2; |
| |
| /** Permission property index. */ |
| private static final byte PROP_PERMISSION_IDX = 3; |
| |
| /** Prefer local writes property index. */ |
| private static final byte PROP_PREFER_LOCAL_WRITES_IDX = 4; |
| |
| /** Trash directory IDs. */ |
| private static final IgniteUuid[] TRASH_IDS; |
| |
| /** Maximum number of file unlock transaction retries when topology changes. */ |
| private static final int MAX_CACHE_TX_RETRIES = IgniteSystemProperties.getInteger(IGNITE_CACHE_RETRIES_COUNT, 100); |
| |
| /** Separator between id and name parts in the trash name. */ |
| private static final char TRASH_NAME_SEPARATOR = '|'; |
| |
| /** Flag: this is a directory. */ |
| private static final byte FLAG_DIR = 0x1; |
| |
| /** Flag: this is a file. */ |
| private static final byte FLAG_FILE = 0x2; |
| |
| /** Filesystem cache prefix. */ |
| public static final String IGFS_CACHE_PREFIX = "igfs-internal-"; |
| |
| /** Data cache suffix. */ |
| public static final String DATA_CACHE_SUFFIX = "-data"; |
| |
| /** Meta cache suffix. */ |
| public static final String META_CACHE_SUFFIX = "-meta"; |
| |
| /** Maximum string length to be written at once. */ |
| private static final int MAX_STR_LEN = 0xFFFF / 4; |
| |
| /** Min available TCP port. */ |
| private static final int MIN_TCP_PORT = 1; |
| |
| /** Max available TCP port. */ |
| private static final int MAX_TCP_PORT = 0xFFFF; |
| |
| /* |
| * Static initializer. |
| */ |
| static { |
| TRASH_IDS = new IgniteUuid[TRASH_CONCURRENCY]; |
| |
| for (int i = 0; i < TRASH_CONCURRENCY; i++) |
| TRASH_IDS[i] = new IgniteUuid(new UUID(0, i + 1), 0); |
| } |
| |
| /** |
| * Get random trash ID. |
| * |
| * @return Trash ID. |
| */ |
| public static IgniteUuid randomTrashId() { |
| return TRASH_IDS[ThreadLocalRandom.current().nextInt(TRASH_CONCURRENCY)]; |
| } |
| |
| /** |
| * Get trash ID for the given index. |
| * |
| * @param idx Index. |
| * @return Trahs ID. |
| */ |
| public static IgniteUuid trashId(int idx) { |
| assert idx >= 0 && idx < TRASH_CONCURRENCY; |
| |
| return TRASH_IDS[idx]; |
| } |
| |
| /** |
| * Check whether provided ID is either root ID or trash ID. |
| * |
| * @param id ID. |
| * @return {@code True} if this is root ID or trash ID. |
| */ |
| public static boolean isRootOrTrashId(@Nullable IgniteUuid id) { |
| return isRootId(id) || isTrashId(id); |
| } |
| |
| /** |
| * Check whether provided ID is root ID. |
| * |
| * @param id ID. |
| * @return {@code True} if this is root ID. |
| */ |
| public static boolean isRootId(@Nullable IgniteUuid id) { |
| return id != null && ROOT_ID.equals(id); |
| } |
| |
| /** |
| * Check whether provided ID is trash ID. |
| * |
| * @param id ID. |
| * @return {@code True} if this is trash ID. |
| */ |
| private static boolean isTrashId(IgniteUuid id) { |
| if (id == null) |
| return false; |
| |
| UUID gid = id.globalId(); |
| |
| return id.localId() == 0 && gid.getMostSignificantBits() == 0 && |
| gid.getLeastSignificantBits() > 0 && gid.getLeastSignificantBits() <= TRASH_CONCURRENCY; |
| } |
| |
| /** |
| * Converts any passed exception to IGFS exception. |
| * |
| * @param err Initial exception. |
| * @return Converted IGFS exception. |
| */ |
| @SuppressWarnings("ThrowableResultOfMethodCallIgnored") |
| public static IgfsException toIgfsException(Throwable err) { |
| IgfsException err0 = err instanceof IgfsException ? (IgfsException)err : null; |
| |
| IgfsException igfsErr = X.cause(err, IgfsException.class); |
| |
| while (igfsErr != null && igfsErr != err0) { |
| err0 = igfsErr; |
| |
| igfsErr = X.cause(err, IgfsException.class); |
| } |
| |
| // If initial exception is already IGFS exception and no inner stuff exists, just return it unchanged. |
| if (err0 != err) { |
| if (err0 != null) |
| // Dealing with a kind of IGFS error, wrap it once again, preserving message and root cause. |
| err0 = newIgfsException(err0.getClass(), err0.getMessage(), err0); |
| else { |
| if (err instanceof ClusterTopologyServerNotFoundException) |
| err0 = new IgfsException("Cache server nodes not found.", err); |
| else |
| // Unknown error nature. |
| err0 = new IgfsException("Generic IGFS error occurred.", err); |
| } |
| } |
| |
| return err0; |
| } |
| |
| /** |
| * Construct new IGFS exception passing specified message and cause. |
| * |
| * @param cls Class. |
| * @param msg Message. |
| * @param cause Cause. |
| * @return New IGFS exception. |
| */ |
| public static IgfsException newIgfsException(Class<? extends IgfsException> cls, String msg, Throwable cause) { |
| try { |
| Constructor<? extends IgfsException> ctor = cls.getConstructor(String.class, Throwable.class); |
| |
| return ctor.newInstance(msg, cause); |
| } |
| catch (ReflectiveOperationException e) { |
| throw new IgniteException("Failed to create IGFS exception: " + cls.getName(), e); |
| } |
| } |
| |
| /** |
| * Constructor. |
| */ |
| private IgfsUtils() { |
| // No-op. |
| } |
| |
| /** |
| * Provides non-null user name. |
| * If the user name is null or empty string, defaults to {@link FileSystemConfiguration#DFLT_USER_NAME}, |
| * which is the current process owner user. |
| * @param user a user name to be fixed. |
| * @return non-null interned user name. |
| */ |
| public static String fixUserName(@Nullable String user) { |
| if (F.isEmpty(user)) |
| user = FileSystemConfiguration.DFLT_USER_NAME; |
| |
| return user; |
| } |
| |
| /** |
| * Performs an operation with transaction with retries. |
| * |
| * @param cache Cache to do the transaction on. |
| * @param clo Closure. |
| * @return Result of closure execution. |
| * @throws IgniteCheckedException If failed. |
| */ |
| @SuppressWarnings("ThrowableResultOfMethodCallIgnored") |
| public static <T> T doInTransactionWithRetries(IgniteInternalCache cache, IgniteOutClosureX<T> clo) |
| throws IgniteCheckedException { |
| assert cache != null; |
| |
| int attempts = 0; |
| |
| while (attempts < MAX_CACHE_TX_RETRIES) { |
| try (Transaction tx = cache.txStart(PESSIMISTIC, REPEATABLE_READ)) { |
| T res = clo.applyx(); |
| |
| tx.commit(); |
| |
| return res; |
| } |
| catch (IgniteException | IgniteCheckedException e) { |
| ClusterTopologyException cte = X.cause(e, ClusterTopologyException.class); |
| |
| if (cte != null) |
| ((IgniteFutureImpl)cte.retryReadyFuture()).internalFuture().getUninterruptibly(); |
| else |
| throw U.cast(e); |
| } |
| |
| attempts++; |
| } |
| |
| throw new IgniteCheckedException("Failed to perform operation since max number of attempts " + |
| "exceeded. [maxAttempts=" + MAX_CACHE_TX_RETRIES + ']'); |
| } |
| |
| /** |
| * Sends a series of event. |
| * |
| * @param kernalCtx Kernal context. |
| * @param path The path of the created file. |
| * @param type The type of event to send. |
| */ |
| public static void sendEvents(GridKernalContext kernalCtx, IgfsPath path, int type) { |
| sendEvents(kernalCtx, path, null, type); |
| } |
| |
| /** |
| * Sends a series of event. |
| * |
| * @param kernalCtx Kernal context. |
| * @param path The path of the created file. |
| * @param newPath New path. |
| * @param type The type of event to send. |
| */ |
| public static void sendEvents(GridKernalContext kernalCtx, IgfsPath path, IgfsPath newPath, int type) { |
| assert kernalCtx != null; |
| assert path != null; |
| |
| GridEventStorageManager evts = kernalCtx.event(); |
| |
| ClusterNode locNode = kernalCtx.discovery().localNode(); |
| |
| if (evts.isRecordable(type)) { |
| if (newPath == null) |
| evts.record(new IgfsEvent(path, locNode, type)); |
| else |
| evts.record(new IgfsEvent(path, newPath, locNode, type)); |
| } |
| } |
| |
| /** |
| * @param cacheName Cache name. |
| * @return {@code True} in this is IGFS data or meta cache. |
| */ |
| public static boolean matchIgfsCacheName(@Nullable String cacheName) { |
| return cacheName != null && cacheName.startsWith(IGFS_CACHE_PREFIX); |
| } |
| |
| /** |
| * @param cfg Grid configuration. |
| * @param cacheName Cache name. |
| * @return {@code True} in this is IGFS data or meta cache. |
| */ |
| public static boolean isIgfsCache(IgniteConfiguration cfg, @Nullable String cacheName) { |
| return matchIgfsCacheName(cacheName); |
| } |
| |
| /** |
| * Prepare cache configuration if this is IGFS meta or data cache. |
| * |
| * @param cfg Configuration. |
| * @throws IgniteCheckedException If failed. |
| */ |
| public static void prepareCacheConfigurations(IgniteConfiguration cfg) throws IgniteCheckedException { |
| FileSystemConfiguration[] igfsCfgs = cfg.getFileSystemConfiguration(); |
| List<CacheConfiguration> ccfgs = new ArrayList<>(Arrays.asList(cfg.getCacheConfiguration())); |
| |
| if (igfsCfgs != null) { |
| for (FileSystemConfiguration igfsCfg : igfsCfgs) { |
| if (igfsCfg == null) |
| continue; |
| |
| CacheConfiguration ccfgMeta = igfsCfg.getMetaCacheConfiguration(); |
| |
| if (ccfgMeta == null) { |
| ccfgMeta = defaultMetaCacheConfig(); |
| |
| igfsCfg.setMetaCacheConfiguration(ccfgMeta); |
| } |
| |
| ccfgMeta.setName(IGFS_CACHE_PREFIX + igfsCfg.getName() + META_CACHE_SUFFIX); |
| |
| ccfgs.add(ccfgMeta); |
| |
| CacheConfiguration ccfgData = igfsCfg.getDataCacheConfiguration(); |
| |
| if (ccfgData == null) { |
| ccfgData = defaultDataCacheConfig(); |
| |
| igfsCfg.setDataCacheConfiguration(ccfgData); |
| } |
| |
| ccfgData.setName(IGFS_CACHE_PREFIX + igfsCfg.getName() + DATA_CACHE_SUFFIX); |
| |
| ccfgs.add(ccfgData); |
| |
| |
| // No copy-on-read. |
| ccfgMeta.setCopyOnRead(false); |
| ccfgData.setCopyOnRead(false); |
| |
| // Always full-sync to maintain consistency. |
| ccfgMeta.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC); |
| ccfgData.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC); |
| |
| // Set co-located affinity mapper if needed. |
| if (igfsCfg.isColocateMetadata() && ccfgMeta.getAffinityMapper() == null) |
| ccfgMeta.setAffinityMapper(new IgfsColocatedMetadataAffinityKeyMapper()); |
| |
| // Set affinity mapper if needed. |
| if (ccfgData.getAffinityMapper() == null) |
| ccfgData.setAffinityMapper(new IgfsGroupDataBlocksKeyMapper()); |
| } |
| |
| cfg.setCacheConfiguration(ccfgs.toArray(new CacheConfiguration[ccfgs.size()])); |
| } |
| |
| validateLocalIgfsConfigurations(cfg); |
| } |
| |
| /** |
| * Validates local IGFS configurations. Compares attributes only for IGFSes with same name. |
| * |
| * @param igniteCfg Ignite config. |
| * @throws IgniteCheckedException If any of IGFS configurations is invalid. |
| */ |
| private static void validateLocalIgfsConfigurations(IgniteConfiguration igniteCfg) |
| throws IgniteCheckedException { |
| |
| if (igniteCfg.getFileSystemConfiguration() == null || igniteCfg.getFileSystemConfiguration().length == 0) |
| return; |
| |
| Collection<String> cfgNames = new HashSet<>(); |
| |
| for (FileSystemConfiguration cfg : igniteCfg.getFileSystemConfiguration()) { |
| String name = cfg.getName(); |
| |
| if (name == null) |
| throw new IgniteCheckedException("IGFS name cannot be null"); |
| |
| if (cfgNames.contains(name)) |
| throw new IgniteCheckedException("Duplicate IGFS name found (check configuration and " + |
| "assign unique name to each): " + name); |
| |
| CacheConfiguration ccfgData = cfg.getDataCacheConfiguration(); |
| |
| CacheConfiguration ccfgMeta = cfg.getMetaCacheConfiguration(); |
| |
| if (QueryUtils.isEnabled(ccfgData)) |
| throw new IgniteCheckedException("IGFS data cache cannot start with enabled query indexing."); |
| |
| if (QueryUtils.isEnabled(ccfgMeta)) |
| throw new IgniteCheckedException("IGFS metadata cache cannot start with enabled query indexing."); |
| |
| if (ccfgMeta.getAtomicityMode() != TRANSACTIONAL) |
| throw new IgniteCheckedException("IGFS metadata cache should be transactional: " + cfg.getName()); |
| |
| if (!(ccfgData.getAffinityMapper() instanceof IgfsGroupDataBlocksKeyMapper)) |
| throw new IgniteCheckedException( |
| "Invalid IGFS data cache configuration (key affinity mapper class should be " + |
| IgfsGroupDataBlocksKeyMapper.class.getSimpleName() + "): " + cfg); |
| |
| IgfsIpcEndpointConfiguration ipcCfg = cfg.getIpcEndpointConfiguration(); |
| |
| if (ipcCfg != null) { |
| final int tcpPort = ipcCfg.getPort(); |
| |
| if (!(tcpPort >= MIN_TCP_PORT && tcpPort <= MAX_TCP_PORT)) |
| throw new IgniteCheckedException("IGFS endpoint TCP port is out of range [" + MIN_TCP_PORT + |
| ".." + MAX_TCP_PORT + "]: " + tcpPort); |
| |
| if (ipcCfg.getThreadCount() <= 0) |
| throw new IgniteCheckedException("IGFS endpoint thread count must be positive: " + |
| ipcCfg.getThreadCount()); |
| } |
| |
| boolean secondary = cfg.getDefaultMode() == IgfsMode.PROXY; |
| |
| if (cfg.getPathModes() != null) { |
| for (Map.Entry<String, IgfsMode> mode : cfg.getPathModes().entrySet()) { |
| if (mode.getValue() == IgfsMode.PROXY) |
| secondary = true; |
| } |
| } |
| |
| if (secondary && cfg.getSecondaryFileSystem() == null) { |
| // When working in any mode except of primary, secondary FS config must be provided. |
| throw new IgniteCheckedException("Grid configuration parameter invalid: " + |
| "secondaryFileSystem cannot be null when mode is not " + IgfsMode.PRIMARY); |
| } |
| |
| cfgNames.add(name); |
| } |
| } |
| |
| |
| /** |
| * @return Default IGFS cache configuration. |
| */ |
| private static CacheConfiguration defaultCacheConfig() { |
| CacheConfiguration cfg = new CacheConfiguration(); |
| cfg.setAtomicityMode(TRANSACTIONAL); |
| cfg.setWriteSynchronizationMode(FULL_SYNC); |
| cfg.setCacheMode(CacheMode.PARTITIONED); |
| |
| return cfg; |
| } |
| |
| /** |
| * @return Default IGFS meta cache configuration. |
| */ |
| private static CacheConfiguration defaultMetaCacheConfig() { |
| CacheConfiguration cfg = defaultCacheConfig(); |
| |
| cfg.setBackups(1); |
| |
| return cfg; |
| } |
| |
| /** |
| * @return Default IGFS data cache configuration. |
| */ |
| private static CacheConfiguration defaultDataCacheConfig() { |
| return defaultCacheConfig(); |
| } |
| |
| /** |
| * Create empty directory with the given ID. |
| * |
| * @param id ID. |
| * @return File info. |
| */ |
| public static IgfsDirectoryInfo createDirectory(IgniteUuid id) { |
| return createDirectory(id, null, null); |
| } |
| |
| /** |
| * Create directory. |
| * |
| * @param id ID. |
| * @param listing Listing. |
| * @param props Properties. |
| * @return File info. |
| */ |
| public static IgfsDirectoryInfo createDirectory( |
| IgniteUuid id, |
| @Nullable Map<String, IgfsListingEntry> listing, |
| @Nullable Map<String, String> props) { |
| long time = System.currentTimeMillis(); |
| |
| return createDirectory(id, listing, props, time, time); |
| } |
| |
| /** |
| * Create directory. |
| * |
| * @param id ID. |
| * @param listing Listing. |
| * @param props Properties. |
| * @param createTime Create time. |
| * @param modificationTime Modification time. |
| * @return File info. |
| */ |
| public static IgfsDirectoryInfo createDirectory( |
| IgniteUuid id, |
| @Nullable Map<String, IgfsListingEntry> listing, |
| @Nullable Map<String,String> props, |
| long createTime, |
| long modificationTime) { |
| return new IgfsDirectoryInfo(id, listing, props, createTime, modificationTime); |
| } |
| |
| /** |
| * Create file. |
| * |
| * @param id File ID. |
| * @param blockSize Block size. |
| * @param len Length. |
| * @param affKey Affinity key. |
| * @param lockId Lock ID. |
| * @param evictExclude Evict exclude flag. |
| * @param props Properties. |
| * @param accessTime Access time. |
| * @param modificationTime Modification time. |
| * @return File info. |
| */ |
| public static IgfsFileInfo createFile(IgniteUuid id, int blockSize, long len, @Nullable IgniteUuid affKey, |
| @Nullable IgniteUuid lockId, boolean evictExclude, @Nullable Map<String, String> props, long accessTime, |
| long modificationTime) { |
| return new IgfsFileInfo(id, blockSize, len, affKey, props, null, lockId, accessTime, modificationTime, |
| evictExclude); |
| } |
| |
| /** |
| * Write listing entry. |
| * |
| * @param out Writer. |
| * @param entry Entry. |
| */ |
| public static void writeListingEntry(BinaryRawWriter out, @Nullable IgfsListingEntry entry) { |
| if (entry != null) { |
| out.writeBoolean(true); |
| |
| BinaryUtils.writeIgniteUuid(out, entry.fileId()); |
| |
| out.writeBoolean(entry.isDirectory()); |
| } |
| else |
| out.writeBoolean(false); |
| } |
| |
| /** |
| * Read listing entry. |
| * |
| * @param in Reader. |
| * @return Entry. |
| */ |
| @Nullable public static IgfsListingEntry readListingEntry(BinaryRawReader in) { |
| if (in.readBoolean()) { |
| IgniteUuid id = BinaryUtils.readIgniteUuid(in); |
| boolean dir = in.readBoolean(); |
| |
| return new IgfsListingEntry(id, dir); |
| } |
| else |
| return null; |
| } |
| |
| /** |
| * Write listing entry. |
| * |
| * @param out Writer. |
| * @param entry Entry. |
| * @throws IOException If failed. |
| */ |
| public static void writeListingEntry(DataOutput out, @Nullable IgfsListingEntry entry) throws IOException { |
| if (entry != null) { |
| out.writeBoolean(true); |
| |
| IgniteUtils.writeGridUuid(out, entry.fileId()); |
| |
| out.writeBoolean(entry.isDirectory()); |
| } |
| else |
| out.writeBoolean(false); |
| } |
| |
| /** |
| * Read listing entry. |
| * |
| * @param in Reader. |
| * @return Entry. |
| * @throws IOException If failed. |
| */ |
| @Nullable public static IgfsListingEntry readListingEntry(DataInput in) throws IOException { |
| if (in.readBoolean()) { |
| IgniteUuid id = IgniteUtils.readGridUuid(in); |
| boolean dir = in.readBoolean(); |
| |
| return new IgfsListingEntry(id, dir); |
| } |
| else |
| return null; |
| } |
| |
| /** |
| * Write entry properties. Rely on reference equality for well-known properties. |
| * |
| * @param out Writer. |
| * @param props Properties. |
| */ |
| @SuppressWarnings("StringEquality") |
| public static void writeProperties(BinaryRawWriter out, @Nullable Map<String, String> props) { |
| if (props != null) { |
| out.writeInt(props.size()); |
| |
| for (Map.Entry<String, String> entry : props.entrySet()) { |
| String key = entry.getKey(); |
| |
| if (key == PROP_PERMISSION) |
| out.writeByte(PROP_PERMISSION_IDX); |
| else if (key == PROP_PREFER_LOCAL_WRITES) |
| out.writeByte(PROP_PREFER_LOCAL_WRITES_IDX); |
| else if (key == PROP_USER_NAME) |
| out.writeByte(PROP_USER_NAME_IDX); |
| else if (key == PROP_GROUP_NAME) |
| out.writeByte(PROP_GROUP_NAME_IDX); |
| else { |
| out.writeByte(PROP_IDX); |
| out.writeString(key); |
| } |
| |
| out.writeString(entry.getValue()); |
| } |
| } |
| else |
| out.writeInt(-1); |
| } |
| |
| /** |
| * Read entry properties. |
| * |
| * @param in Reader. |
| * @return Properties. |
| */ |
| @Nullable public static Map<String, String> readProperties(BinaryRawReader in) { |
| int size = in.readInt(); |
| |
| if (size >= 0) { |
| Map<String, String> props = new HashMap<>(size); |
| |
| for (int i = 0; i < size; i++) { |
| byte idx = in.readByte(); |
| |
| String key; |
| |
| switch (idx) { |
| case PROP_PERMISSION_IDX: |
| key = PROP_PERMISSION; |
| |
| break; |
| |
| case PROP_PREFER_LOCAL_WRITES_IDX: |
| key = PROP_PREFER_LOCAL_WRITES; |
| |
| break; |
| |
| case PROP_USER_NAME_IDX: |
| key = PROP_USER_NAME; |
| |
| break; |
| |
| case PROP_GROUP_NAME_IDX: |
| key = PROP_GROUP_NAME; |
| |
| break; |
| |
| default: |
| key = in.readString(); |
| } |
| |
| props.put(key, in.readString()); |
| } |
| |
| return props; |
| } |
| else |
| return null; |
| } |
| |
| /** |
| * Write entry properties. Rely on reference equality for well-known properties. |
| * |
| * @param out Writer. |
| * @param props Properties. |
| * @throws IOException If failed. |
| */ |
| @SuppressWarnings("StringEquality") |
| public static void writeProperties(DataOutput out, @Nullable Map<String, String> props) throws IOException { |
| if (props != null) { |
| out.writeInt(props.size()); |
| |
| for (Map.Entry<String, String> entry : props.entrySet()) { |
| String key = entry.getKey(); |
| |
| if (key == PROP_PERMISSION) |
| out.writeByte(PROP_PERMISSION_IDX); |
| else if (key == PROP_PREFER_LOCAL_WRITES) |
| out.writeByte(PROP_PREFER_LOCAL_WRITES_IDX); |
| else if (key == PROP_USER_NAME) |
| out.writeByte(PROP_USER_NAME_IDX); |
| else if (key == PROP_GROUP_NAME) |
| out.writeByte(PROP_GROUP_NAME_IDX); |
| else { |
| out.writeByte(PROP_IDX); |
| U.writeString(out, key); |
| } |
| |
| U.writeString(out, entry.getValue()); |
| } |
| } |
| else |
| out.writeInt(-1); |
| } |
| |
| /** |
| * Read entry properties. |
| * |
| * @param in Reader. |
| * @return Properties. |
| * @throws IOException If failed. |
| */ |
| @Nullable public static Map<String, String> readProperties(DataInput in) throws IOException { |
| int size = in.readInt(); |
| |
| if (size >= 0) { |
| Map<String, String> props = new HashMap<>(size); |
| |
| for (int i = 0; i < size; i++) { |
| byte idx = in.readByte(); |
| |
| String key; |
| |
| switch (idx) { |
| case PROP_PERMISSION_IDX: |
| key = PROP_PERMISSION; |
| |
| break; |
| |
| case PROP_PREFER_LOCAL_WRITES_IDX: |
| key = PROP_PREFER_LOCAL_WRITES; |
| |
| break; |
| |
| case PROP_USER_NAME_IDX: |
| key = PROP_USER_NAME; |
| |
| break; |
| |
| case PROP_GROUP_NAME_IDX: |
| key = PROP_GROUP_NAME; |
| |
| break; |
| |
| default: |
| key = U.readString(in); |
| } |
| |
| props.put(key, U.readString(in)); |
| } |
| |
| return props; |
| } |
| else |
| return null; |
| } |
| |
| /** |
| * Write IGFS path. |
| * |
| * @param writer Writer. |
| * @param path Path. |
| */ |
| public static void writePath(BinaryRawWriter writer, @Nullable IgfsPath path) { |
| if (path != null) { |
| writer.writeBoolean(true); |
| |
| path.writeRawBinary(writer); |
| } |
| else |
| writer.writeBoolean(false); |
| } |
| |
| /** |
| * Read IGFS path. |
| * |
| * @param reader Reader. |
| * @return Path. |
| */ |
| @Nullable public static IgfsPath readPath(BinaryRawReader reader) { |
| if (reader.readBoolean()) { |
| IgfsPath path = new IgfsPath(); |
| |
| path.readRawBinary(reader); |
| |
| return path; |
| } |
| else |
| return null; |
| } |
| |
| /** |
| * Read non-null path from the input. |
| * |
| * @param in Input. |
| * @return IGFS path. |
| * @throws IOException If failed. |
| */ |
| public static IgfsPath readPath(ObjectInput in) throws IOException { |
| IgfsPath res = new IgfsPath(); |
| |
| res.readExternal(in); |
| |
| return res; |
| } |
| |
| /** |
| * Write IgfsFileAffinityRange. |
| * |
| * @param writer Writer |
| * @param affRange affinity range. |
| */ |
| public static void writeFileAffinityRange(BinaryRawWriter writer, @Nullable IgfsFileAffinityRange affRange) { |
| if (affRange != null) { |
| writer.writeBoolean(true); |
| |
| affRange.writeRawBinary(writer); |
| } |
| else |
| writer.writeBoolean(false); |
| } |
| |
| /** |
| * Read IgfsFileAffinityRange. |
| * |
| * @param reader Reader. |
| * @return File affinity range. |
| */ |
| public static IgfsFileAffinityRange readFileAffinityRange(BinaryRawReader reader) { |
| if (reader.readBoolean()) { |
| IgfsFileAffinityRange affRange = new IgfsFileAffinityRange(); |
| |
| affRange.readRawBinary(reader); |
| |
| return affRange; |
| } |
| else |
| return null; |
| } |
| |
| /** |
| * Parses the TRASH file name to extract the original path. |
| * |
| * @param name The TRASH short (entry) name. |
| * @return The original path, or null in case of failure. |
| */ |
| public static IgfsPath extractOriginalPathFromTrash(String name) { |
| int idx = name.indexOf(TRASH_NAME_SEPARATOR); |
| |
| assert idx >= 0; |
| |
| String path = name.substring(idx + 1, name.length()); |
| |
| return new IgfsPath(path); |
| } |
| |
| /** |
| * Creates short name of the file in TRASH directory. |
| * The name consists of the whole file path and its unique id. |
| * Upon file cleanup this name will be parsed to extract the path. |
| * Note that in contrast to common practice the composed name contains '/' character. |
| * |
| * @param path The full path of the deleted file. |
| * @param id The file id. |
| * @return The new short name for trash directory. |
| */ |
| static String composeNameForTrash(IgfsPath path, IgniteUuid id) { |
| return id.toString() + TRASH_NAME_SEPARATOR + path.toString(); |
| } |
| |
| /** |
| * Check whether provided node contains IGFS with the given name. |
| * |
| * @param node Node. |
| * @param igfsName IGFS name. |
| * @return {@code True} if it contains IGFS. |
| */ |
| public static boolean isIgfsNode(ClusterNode node, String igfsName) { |
| assert node != null; |
| |
| IgfsAttributes[] igfs = node.attribute(ATTR_IGFS); |
| |
| if (igfs != null) |
| for (IgfsAttributes attrs : igfs) |
| if (F.eq(igfsName, attrs.igfsName())) |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * Check whether mode is dual. |
| * |
| * @param mode Mode. |
| * @return {@code True} if dual. |
| */ |
| public static boolean isDualMode(IgfsMode mode) { |
| return mode == DUAL_SYNC || mode == DUAL_ASYNC; |
| } |
| |
| /** |
| * Answers if directory of this mode can contain a subdirectory of the given mode. |
| * |
| * @param parent Parent mode. |
| * @param child Child mode. |
| * @return {@code true} if directory of this mode can contain a directory of the given mode. |
| */ |
| public static boolean canContain(IgfsMode parent, IgfsMode child) { |
| return isDualMode(parent) || parent == child; |
| } |
| |
| /** |
| * Checks, filters and sorts the modes. |
| * |
| * @param dfltMode The root mode. Must always be not null. |
| * @param modes The subdirectory modes. |
| * @param dualParentsContainingPrimaryChildren The set to store parents into. |
| * @return Descending list of filtered and checked modes. |
| * @throws IgniteCheckedException On error. |
| */ |
| public static ArrayList<T2<IgfsPath, IgfsMode>> preparePathModes(final IgfsMode dfltMode, |
| @Nullable List<T2<IgfsPath, IgfsMode>> modes, Set<IgfsPath> dualParentsContainingPrimaryChildren) |
| throws IgniteCheckedException { |
| if (modes == null) |
| return null; |
| |
| // Sort by depth, shallow first. |
| Collections.sort(modes, new Comparator<Map.Entry<IgfsPath, IgfsMode>>() { |
| @Override public int compare(Map.Entry<IgfsPath, IgfsMode> o1, Map.Entry<IgfsPath, IgfsMode> o2) { |
| return o1.getKey().depth() - o2.getKey().depth(); |
| } |
| }); |
| |
| ArrayList<T2<IgfsPath, IgfsMode>> resModes = new ArrayList<>(modes.size() + 1); |
| |
| resModes.add(new T2<>(IgfsPath.ROOT, dfltMode)); |
| |
| for (T2<IgfsPath, IgfsMode> mode : modes) { |
| assert mode.getKey() != null; |
| |
| for (T2<IgfsPath, IgfsMode> resMode : resModes) { |
| if (mode.getKey().isSubDirectoryOf(resMode.getKey())) { |
| assert resMode.getValue() != null; |
| |
| if (resMode.getValue() == mode.getValue()) |
| // No reason to add a sub-path of the same mode, ignore this pair. |
| break; |
| |
| if (!canContain(resMode.getValue(), mode.getValue())) |
| throw new IgniteCheckedException("Subdirectory " + mode.getKey() + " mode " |
| + mode.getValue() + " is not compatible with upper level " |
| + resMode.getKey() + " directory mode " + resMode.getValue() + "."); |
| |
| // Add to the 1st position (deep first). |
| resModes.add(0, mode); |
| |
| // Store primary paths inside dual paths in separate collection: |
| if (mode.getValue() == PRIMARY) |
| dualParentsContainingPrimaryChildren.add(mode.getKey().parent()); |
| |
| break; |
| } |
| } |
| } |
| |
| // Remove root, because this class contract is that root mode is not contained in the list. |
| resModes.remove(resModes.size() - 1); |
| |
| return resModes; |
| } |
| |
| /** |
| * Create flags value. |
| * |
| * @param isDir Directory flag. |
| * @param isFile File flag. |
| * @return Result. |
| */ |
| public static byte flags(boolean isDir, boolean isFile) { |
| byte res = isDir ? FLAG_DIR : 0; |
| |
| if (isFile) |
| res |= FLAG_FILE; |
| |
| return res; |
| } |
| |
| /** |
| * Check whether passed flags represent directory. |
| * |
| * @param flags Flags. |
| * @return {@code True} if this is directory. |
| */ |
| public static boolean isDirectory(byte flags) { |
| return hasFlag(flags, FLAG_DIR); |
| } |
| |
| /** |
| * Check whether passed flags represent file. |
| * |
| * @param flags Flags. |
| * @return {@code True} if this is file. |
| */ |
| public static boolean isFile(byte flags) { |
| return hasFlag(flags, FLAG_FILE); |
| } |
| |
| /** |
| * Check whether certain flag is set. |
| * |
| * @param flags Flags. |
| * @param flag Flag to check. |
| * @return {@code True} if flag is set. |
| */ |
| private static boolean hasFlag(byte flags, byte flag) { |
| return (flags & flag) == flag; |
| } |
| |
| /** |
| * Reads string-to-string map written by {@link #writeStringMap(DataOutput, Map)}. |
| * |
| * @param in Data input. |
| * @throws IOException If write failed. |
| * @return Read result. |
| */ |
| public static Map<String, String> readStringMap(DataInput in) throws IOException { |
| int size = in.readInt(); |
| |
| if (size == -1) |
| return null; |
| else { |
| Map<String, String> map = U.newHashMap(size); |
| |
| for (int i = 0; i < size; i++) |
| map.put(readUTF(in), readUTF(in)); |
| |
| return map; |
| } |
| } |
| |
| /** |
| * Writes string-to-string map to given data output. |
| * |
| * @param out Data output. |
| * @param map Map. |
| * @throws IOException If write failed. |
| */ |
| public static void writeStringMap(DataOutput out, @Nullable Map<String, String> map) throws IOException { |
| if (map != null) { |
| out.writeInt(map.size()); |
| |
| for (Map.Entry<String, String> e : map.entrySet()) { |
| writeUTF(out, e.getKey()); |
| writeUTF(out, e.getValue()); |
| } |
| } |
| else |
| out.writeInt(-1); |
| } |
| |
| /** |
| * Write UTF string which can be {@code null}. |
| * |
| * @param out Output stream. |
| * @param val Value. |
| * @throws IOException If failed. |
| */ |
| public static void writeUTF(DataOutput out, @Nullable String val) throws IOException { |
| if (val == null) |
| out.writeInt(-1); |
| else { |
| out.writeInt(val.length()); |
| |
| if (val.length() <= MAX_STR_LEN) |
| out.writeUTF(val); // Optimized write in 1 chunk. |
| else { |
| int written = 0; |
| |
| while (written < val.length()) { |
| int partLen = Math.min(val.length() - written, MAX_STR_LEN); |
| |
| String part = val.substring(written, written + partLen); |
| |
| out.writeUTF(part); |
| |
| written += partLen; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Read UTF string which can be {@code null}. |
| * |
| * @param in Input stream. |
| * @return Value. |
| * @throws IOException If failed. |
| */ |
| public static String readUTF(DataInput in) throws IOException { |
| int len = in.readInt(); // May be zero. |
| |
| if (len < 0) |
| return null; |
| else { |
| if (len <= MAX_STR_LEN) |
| return in.readUTF(); |
| |
| StringBuilder sb = new StringBuilder(len); |
| |
| do { |
| sb.append(in.readUTF()); |
| } |
| while (sb.length() < len); |
| |
| assert sb.length() == len; |
| |
| return sb.toString(); |
| } |
| } |
| } |