Java tutorial
/** * The MIT License (MIT) * * Copyright (c) 2011-2016 Incapture Technologies LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package rapture.sheet.file; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.net.HttpURLConnection; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import rapture.common.RaptureFolderInfo; import rapture.common.RaptureSheet; import rapture.common.RaptureSheetCell; import rapture.common.RaptureSheetNote; import rapture.common.RaptureSheetRange; import rapture.common.RaptureSheetScript; import rapture.common.RaptureSheetStyle; import rapture.common.exception.ExceptionToString; import rapture.common.exception.RaptNotSupportedException; import rapture.common.exception.RaptureExceptionFactory; import rapture.kernel.file.FileRepoUtils; import rapture.sheet.SheetStore; import com.google.common.collect.ImmutableList; /** * A FileSheetStore is a simple directory/file implementation of a SheetStore. * * * @author dtong * */ public class FileSheetStore implements SheetStore { private static Logger log = Logger.getLogger(FileSheetStore.class); private String authority; private Map<String, FileSheetStatus> sheetStatus = new HashMap<String, FileSheetStatus>(); public static final String PREFIX = "prefix"; private File parentDir = null; private FileMetaSheetStore metaStore; private FileSheetStatus nullSheetStatus = new FileSheetStatus(); @Override public void setConfig(String authority, Map<String, String> config) { // What happens if this is called twice? if (parentDir != null) throw RaptureExceptionFactory.create("Calling setConfig twice is currently not supported"); this.authority = authority; String prefix = config.get(FileRepoUtils.PREFIX); if (StringUtils.trimToNull(prefix) == null) throw RaptureExceptionFactory.create("prefix must be specified"); parentDir = FileRepoUtils.ensureDirectory(prefix + "_sheet"); metaStore = new FileMetaSheetStore(); metaStore.setConfig(config); } /** * Epoch number implmentation is version dependent. Mongo generates it automatically; We have to set our own. */ @Override public void setCell(String sheetName, int row, int column, String string, int dimension) { FileSheetStatus sheet = getSheetStatus(sheetName, 1L); if (sheet != this.nullSheetStatus) { long epoch = System.currentTimeMillis(); RaptureSheetCell cell = new RaptureSheetCell(row, column, string, epoch); sheet.addCell(cell); sheet.setEpoch(epoch); writeSheet(sheetName, ImmutableList.of(cell), true); } } /** * Get from cache if possible * * @param sheetName * @return */ private FileSheetStatus getSheetStatus(String sheetName, Long epoch) { if (sheetStatus.containsKey(sheetName)) { FileSheetStatus ret = sheetStatus.get(sheetName); if (ret == null) { return nullSheetStatus; } return ret; } // Attempt to load return loadSheet(sheetName, epoch); } @Override public Boolean sheetExists(String docPath) { File sheetFile = FileRepoUtils.makeGenericFile(parentDir, docPath); return sheetFile.exists() && sheetFile.isFile(); } /** * Load directly, do not try to read from cache. * @param sheetName * @return */ private FileSheetStatus loadSheet(String sheetName, long epoch) { File sheetFile = FileRepoUtils.makeGenericFile(parentDir, sheetName); if (!sheetFile.exists() || !sheetFile.isFile()) return nullSheetStatus; try { List<String> lines = Files.readAllLines(sheetFile.toPath(), StandardCharsets.UTF_8); FileSheetStatus status = new FileSheetStatus(); for (String line : lines) { String splitChar = line.substring(0, 1); String[] fields = line.substring(1).split(splitChar); RaptureSheetCell cell = new RaptureSheetCell(); long tmpEpoch = Long.parseLong(fields[2]); if (tmpEpoch >= epoch) { cell.setRow(Integer.parseInt(fields[0])); cell.setColumn(Integer.parseInt(fields[1])); cell.setEpoch(tmpEpoch); cell.setData(fields[3]); status.addCell(cell); } } sheetStatus.put(sheetName, status); return status; } catch (IOException | NumberFormatException e) { log.info("Cannot load sheet " + sheetName, e); log.debug(ExceptionToString.format(e)); } return nullSheetStatus; } @Override public String getCell(String sheetName, int row, int column, int dimension) { // What is dimension? FileSheetStatus status = getSheetStatus(sheetName, 1L); for (RaptureSheetCell cell : status.getCells()) { if ((cell.getRow() == row) && (cell.getColumn() == column)) { return cell.getData(); } } return null; } @Override public RaptureSheet createSheet(String name) { // create an empty sheet writeSheet(name, new ArrayList<RaptureSheetCell>(0), false); RaptureSheet sheet = new RaptureSheet(); sheet.setName(name); sheet.setAuthority(authority); return sheet; } private void writeSheet(String name, Collection<RaptureSheetCell> cells, boolean append) { File sheetFile = FileRepoUtils.makeGenericFile(parentDir, name); if (sheetFile.exists() && !sheetFile.isFile()) throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST, "Cannot write to sheet " + sheetFile.getName() + " - not a file"); try { sheetFile.getParentFile().mkdirs(); try (PrintStream out = new PrintStream(new FileOutputStream(sheetFile, append))) { appendCells(out, cells); } } catch (SecurityException | IOException e) { // Very unlikely throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST, "Cannot write to sheet " + sheetFile.getName(), e); } return; } private void appendCells(PrintStream stream, Collection<RaptureSheetCell> cells) { for (RaptureSheetCell cell : cells) { char i = 32; String data = cell.getData(); if (data == null) continue; // cell has been deleted // Find a delimeter that is available. // NOTE: if negative column numbers are allowed then we can't use minus sign while ((data.indexOf(i) >= 0) || Character.isDigit(i) || (i == '-')) i++; stream.append(i); stream.append(("" + cell.getRow())); stream.append(i); stream.append(("" + cell.getColumn())); stream.append(i); stream.append(("" + cell.getEpoch())); stream.append(i); stream.append(data.replaceAll("\n", "")); // newline not allowed stream.append('\n'); } } @Override public RaptureSheet deleteSheet(String name) { File sheetFile = FileRepoUtils.makeGenericFile(parentDir, name); if (!sheetFile.exists() || !sheetFile.isFile()) return null; List<RaptureSheetNote> notelist = metaStore.getSheetNotes(name); for (RaptureSheetNote note : notelist) { metaStore.deleteSheetNote(name, note.getId()); } sheetFile.delete(); // Serves the purpose of DefaultFolderCleanupService for mongo implementation File parent = new File(sheetFile.getParent()); if (parent.list().length == 0) { parent.delete(); } RaptureSheet sheet = new RaptureSheet(); sheet.setName(name); sheet.setAuthority(authority); return sheet; } @Override public List<RaptureFolderInfo> listSheetsByUriPrefix(String displayNamePart) { File file = FileRepoUtils.makeGenericFile(parentDir, displayNamePart); File[] children = file.listFiles(); List<RaptureFolderInfo> info = new ArrayList<>((children == null) ? 0 : children.length); if ((children != null) && (children.length > 0)) { for (File kid : children) { RaptureFolderInfo inf = new RaptureFolderInfo(); inf.setName(kid.getName()); inf.setFolder(kid.isDirectory()); info.add(inf); } } return info; } @Override public List<RaptureSheetCell> findCellsByEpoch(String name, int dimension, long epoch) { return getSheetStatus(name, epoch).getCells(); } @Override public void cloneSheet(String srcName, String targetName) { FileSheetStatus sheet = getSheetStatus(srcName, 1L); writeSheet(targetName, sheet.getCells(), false); } @Override public Boolean deleteSheetColumn(String docPath, int column) { FileSheetStatus sheet = getSheetStatus(docPath, 1L); long epoch = System.currentTimeMillis(); sheet.setEpoch(epoch); for (RaptureSheetCell cell : sheet.getCells()) { if (cell.getColumn() == column) cell.setData(null); } writeSheet(docPath, sheet.getCells(), false); return true; } @Override public Boolean deleteSheetRow(String docPath, int row) { FileSheetStatus sheet = getSheetStatus(docPath, 1L); long epoch = System.currentTimeMillis(); sheet.setEpoch(epoch); for (RaptureSheetCell cell : sheet.getCells()) { if (cell.getRow() == row) cell.setData(null); } writeSheet(docPath, sheet.getCells(), false); return true; } @Override public Boolean deleteSheetCell(String docPath, int row, int column, int dimension) { FileSheetStatus sheet = getSheetStatus(docPath, 1L); long epoch = System.currentTimeMillis(); sheet.setEpoch(epoch); RaptureSheetCell cell = new RaptureSheetCell(); cell.setColumn(column); cell.setRow(row); cell.setEpoch(epoch); cell.setData(null); sheet.addCell(cell); writeSheet(docPath, sheet.getCells(), false); return true; } @Override public Boolean setBlock(String docPath, int startRow, int startColumn, List<String> values, int width, int height, int dimension) { long epoch = System.currentTimeMillis(); int currentRow = startRow; int currentColumn = startColumn; int columnCount = 0; // We can append the cells to the file. Don't need to rewrite all of them. Collection<RaptureSheetCell> cells = new ArrayList<>(); FileSheetStatus sheet = getSheetStatus(docPath, 1L); sheet.setEpoch(epoch); for (String value : values) { RaptureSheetCell cell = new RaptureSheetCell(); cell.setColumn(currentColumn); cell.setRow(currentRow); cell.setEpoch(epoch); cell.setData((value == null) ? "" : value); cells.add(cell); sheet.addCell(cell); currentColumn++; columnCount++; if (columnCount >= width) { currentRow++; currentColumn = startColumn; columnCount = 0; } } writeSheet(docPath, cells, true); return true; } @Override public void drop() { if (parentDir != null) { try { FileUtils.deleteDirectory(parentDir); parentDir = null; } catch (IOException e) { throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST, "Cannot drop sheet store", e); } } } @Override public List<RaptureSheetStyle> getSheetStyles(String name) { return metaStore.getSheetStyles(name); } @Override public Boolean deleteSheetStyle(String name, String styleName) { return metaStore.deleteSheetStyle(name, styleName); } @Override public RaptureSheetStyle putSheetStyle(String name, RaptureSheetStyle style) { return metaStore.putSheetStyle(name, style); } @Override public List<RaptureSheetScript> getSheetScripts(String name) { return metaStore.getSheetScripts(name); } @Override public Boolean deleteSheetScript(String name, String scriptName) { return metaStore.deleteSheetScript(name, scriptName); } @Override public RaptureSheetScript putSheetScript(String name, String scriptName, RaptureSheetScript script) { return metaStore.putSheetScript(name, scriptName, script); } @Override public List<RaptureSheetRange> getSheetNamedSelections(String name) { return metaStore.getSheetNamedSelections(name); } @Override public Boolean deleteSheetNamedSelection(String name, String rangeName) { return metaStore.deleteSheetNamedSelection(name, rangeName); } @Override public RaptureSheetRange putSheetNamedSelection(String name, String rangeName, RaptureSheetRange range) { return metaStore.putSheetNamedSelection(name, rangeName, range); } @Override public List<RaptureSheetNote> getSheetNotes(String name) { return metaStore.getSheetNotes(name); } @Override public Boolean deleteSheetNote(String name, String noteId) { return metaStore.deleteSheetNote(name, noteId); } @Override public RaptureSheetNote putSheetNote(String name, RaptureSheetNote note) { return metaStore.putSheetNote(name, note); } @Override public RaptureSheetScript getSheetScript(String name, String scriptName) { return metaStore.getSheetScript(name, scriptName); } @Override public RaptureSheetRange getSheetNamedSelection(String name, String rangeName) { return metaStore.getSheetNamedSelection(name, rangeName); } }