/* * JBoss, Home of Professional Open Source * Copyright 2010 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.io; import org.infinispan.AdvancedCache; import org.infinispan.Cache; import org.jgroups.util.Util; import java.io.Externalizable; import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.Set; import static java.lang.String.format; import static org.infinispan.context.Flag.FORCE_SYNCHRONOUS; /** * Subclass of File to iterate through directories and files in a grid * * @author Bela Ban * @author Marko Luksa */ public class GridFile extends File { private static final long serialVersionUID = 552534285862004134L; private static final Metadata ROOT_DIR_METADATA = new Metadata(0, 0, 0, Metadata.DIR); private static final char SEPARATOR_CHAR = '/'; private static final String SEPARATOR = "" + SEPARATOR_CHAR; private final AdvancedCache<String, Metadata> metadataCache; private final GridFilesystem fs; private final String path; private int chunkSize; GridFile(String pathname, Cache<String, Metadata> metadataCache, int chunkSize, GridFilesystem fs) { super(pathname); this.fs = fs; this.path = formatPath(pathname); this.metadataCache = metadataCache.getAdvancedCache(); this.chunkSize = chunkSize; initChunkSizeFromMetadata(); } GridFile(String parent, String child, Cache<String, Metadata> metadataCache, int chunkSize, GridFilesystem fs) { this(parent + File.separator + child, metadataCache, chunkSize, fs); } GridFile(File parent, String child, Cache<String, Metadata> metadataCache, int chunkSize, GridFilesystem fs) { this(parent.getPath(), child, metadataCache, chunkSize, fs); } @Override public String getName() { return filename(getPath()); } /** * Returns path of this file. To avoid issues arising from file separator differences between different * operative systems, the path returned always uses Unix-like path separator, '/' character. Any client * code calling this method should bear that if disecting the path. * * @return String containing path of file. */ @Override public String getPath() { return path; } @Override public String getAbsolutePath() { return convertToAbsolute(getPath()); } @Override public File getAbsoluteFile() { return new GridFile(getAbsolutePath(), metadataCache, getChunkSize(), fs); } @Override public boolean isAbsolute() { return getPath().startsWith(SEPARATOR); } private String convertToAbsolute(String path) { if (!path.startsWith(SEPARATOR)) return SEPARATOR + path; else return path; } private static String formatPath(String path) { if (path == null) return null; // Regardless of platform, always use the same separator char, otherwise // keys might not be found when transfering metadata between different OS path = path.replace('\\', SEPARATOR_CHAR); if (path != null && path.endsWith(SEPARATOR)) { int index = path.lastIndexOf(SEPARATOR); if (index != -1) path = path.substring(0, index); } return path; } @Override public long length() { Metadata metadata = getMetadata(); if (metadata != null) return metadata.length; return 0; } private Metadata getMetadata() { if (isRootDir()) { return ROOT_DIR_METADATA; } return metadataCache.get(getAbsolutePath()); } private boolean isRootDir() { return "/".equals(getAbsolutePath()); } void setLength(int newLength) { Metadata metadata = getMetadata(); if (metadata == null) throw new IllegalStateException("metadata for " + getAbsolutePath() + " not found."); metadata.setLength(newLength); metadata.setModificationTime(System.currentTimeMillis()); metadataCache.put(getAbsolutePath(), metadata); } public int getChunkSize() { return chunkSize; } @Override public boolean createNewFile() throws IOException { if (exists()) return false; if (!checkParentDirs(getAbsolutePath(), false)) throw new IOException("Cannot create file " + getAbsolutePath() + " (parent dir does not exist)"); metadataCache.withFlags(FORCE_SYNCHRONOUS).put(getAbsolutePath(), new Metadata(0, System.currentTimeMillis(), chunkSize, Metadata.FILE)); return true; } @Override public boolean delete() { return delete(false); // asynchronous delete by default } public boolean delete(boolean synchronous) { if (!exists()) return false; if (isDirectory() && hasChildren()) return false; fs.remove(getAbsolutePath(), synchronous); // removes all the chunks belonging to the file if (synchronous) metadataCache.withFlags(FORCE_SYNCHRONOUS).remove(getAbsolutePath()); // removes the metadata information else metadataCache.remove(getAbsolutePath()); // removes the metadata information return true; } private boolean hasChildren() { File[] files = listFiles(); return files != null && files.length > 0; } @Override public boolean mkdir() { return mkdir(false); } @Override public boolean mkdirs() { return mkdir(true); } private boolean mkdir(boolean alsoCreateParentDirs) { try { boolean parentsExist = checkParentDirs(getAbsolutePath(), alsoCreateParentDirs); if (!parentsExist) return false; metadataCache.withFlags(FORCE_SYNCHRONOUS).put(getAbsolutePath(),new Metadata(0, System.currentTimeMillis(), chunkSize, Metadata.DIR)); return true; } catch (IOException e) { return false; } } @Override public boolean exists() { return getMetadata() != null; } @Override public String getParent() { return formatPath(super.getParent()); } @Override public File getParentFile() { String parentPath = getParent(); if (parentPath == null) return null; return new GridFile(parentPath, metadataCache, chunkSize, fs); } @Override public long lastModified() { Metadata metadata = getMetadata(); return metadata == null ? 0 : metadata.getModificationTime(); } @Override public String[] list() { return list(null); } @Override public String[] list(FilenameFilter filter) { return _list(filter); } @Override public File[] listFiles() { return listFiles((FilenameFilter) null); } @Override public File[] listFiles(FilenameFilter filter) { return _listFiles(filter); } @Override public File[] listFiles(FileFilter filter) { return _listFiles(filter); } @Override public boolean isDirectory() { Metadata metadata = getMetadata(); return metadata != null && metadata.isDirectory(); } @Override public boolean isFile() { Metadata metadata = getMetadata(); return metadata != null && metadata.isFile(); } protected void initChunkSizeFromMetadata() { Metadata metadata = getMetadata(); if (metadata != null) this.chunkSize = metadata.getChunkSize(); } protected File[] _listFiles(Object filter) { String[] filenames = _list(filter); return convertFilenamesToFiles(filenames); } private File[] convertFilenamesToFiles(String[] files) { if (files == null) return null; File[] retval = new File[files.length]; for (int i = 0; i < files.length; i++) retval[i] = new GridFile(this, files[i], metadataCache, chunkSize, fs); return retval; } protected String[] _list(Object filter) { if (!isDirectory()) return null; Set<String> paths = metadataCache.keySet(); Collection<String> list = new LinkedList<String>(); for (String path : paths) { if (isChildOf(getAbsolutePath(), path)) { if (filter instanceof FilenameFilter && !((FilenameFilter) filter).accept(this, filename(path))) continue; else if (filter instanceof FileFilter && !((FileFilter) filter).accept(new File(path))) continue; list.add(filename(path)); } } return list.toArray(new String[list.size()]); } /** * Verifies whether child is a child (dir or file) of parent * * @param parent * @param child * @return True if child is a child, false otherwise */ protected static boolean isChildOf(String parent, String child) { if (parent == null || child == null) return false; if (!child.startsWith(parent)) return false; if (child.length() <= parent.length()) return false; int from = parent.equals(SEPARATOR) ? parent.length() : parent.length() + 1; // if(from-1 > child.length()) // return false; String[] comps = Util.components(child.substring(from), SEPARATOR); return comps != null && comps.length <= 1; } protected static String filename(String fullPath) { String[] comps = Util.components(fullPath, SEPARATOR); return comps != null ? comps[comps.length - 1] : ""; } /** * Checks whether the parent directories are present (and are directories). If createIfAbsent is true, * creates missing dirs * * @param path * @param createIfAbsent * @return */ protected boolean checkParentDirs(String path, boolean createIfAbsent) throws IOException { String[] components = Util.components(path, SEPARATOR); if (components == null) return false; if (components.length == 1) // no parent directories to create, e.g. "data.txt" return true; StringBuilder sb = new StringBuilder(); boolean first = true; for (int i = 0; i < components.length - 1; i++) { String tmp = components[i]; if (!tmp.equals(SEPARATOR)) { if (first) first = false; else sb.append(SEPARATOR); } sb.append(tmp); String comp = sb.toString(); if (comp.equals(SEPARATOR)) continue; Metadata val = exists(comp); if (val != null) { if (val.isFile()) throw new IOException(format("cannot create %s as component %s is a file", path, comp)); } else if (createIfAbsent) { metadataCache.put(comp, new Metadata(0, System.currentTimeMillis(), chunkSize, Metadata.DIR)); } else { // Couldn't find a component and we're not allowed to create components! return false; } } // check that we have found all the components we need. return true; } @Override public boolean equals(Object obj) { if ((obj != null) && (obj instanceof GridFile)) { return compareTo((GridFile)obj) == 0; } return false; } @Override public int compareTo(File file) { return getAbsolutePath().compareTo(file.getAbsolutePath()); } @Override public int hashCode() { return getAbsolutePath().hashCode(); } private Metadata exists(String key) { return metadataCache.get(key); } public static class Metadata implements Externalizable { public static final byte FILE = 1; public static final byte DIR = 1 << 1; private int length = 0; private long modificationTime = 0; private int chunkSize = 0; private byte flags = 0; public Metadata() { } public Metadata(int length, long modificationTime, int chunkSize, byte flags) { this.length = length; this.modificationTime = modificationTime; this.chunkSize = chunkSize; this.flags = flags; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public long getModificationTime() { return modificationTime; } public void setModificationTime(long modificationTime) { this.modificationTime = modificationTime; } public int getChunkSize() { return chunkSize; } public boolean isFile() { return Util.isFlagSet(flags, FILE); } public boolean isDirectory() { return Util.isFlagSet(flags, DIR); } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getType()); if (isFile()) sb.append(", len=" + Util.printBytes(length) + ", chunkSize=" + chunkSize); sb.append(", modTime=").append(new Date(modificationTime)); return sb.toString(); } private String getType() { if (isFile()) return "file"; if (isDirectory()) return "dir"; return "n/a"; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(length); out.writeLong(modificationTime); out.writeInt(chunkSize); out.writeByte(flags); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { length = in.readInt(); modificationTime = in.readLong(); chunkSize = in.readInt(); flags = in.readByte(); } } }