package com.mozilla.grouperfish.services.local; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mozilla.grouperfish.base.Assert; import com.mozilla.grouperfish.base.StreamTool; import com.mozilla.grouperfish.services.api.FileSystem; import static java.lang.String.format; public class LocalFileSystem implements FileSystem { private static final Logger log = LoggerFactory.getLogger(LocalFileSystem.class); private final File rootDir; private final String rootPath; public LocalFileSystem(final String root) { Assert.nonNull(root); Assert.check(!root.isEmpty()); rootDir = new File(root); Assert.check(!rootDir.exists() || rootDir.isDirectory()); try { if (!rootDir.exists()) rootDir.mkdirs(); rootPath = rootDir.getCanonicalPath(); } catch (final IOException e) { throw new RuntimeException(root); } log.info("Instantiated service: {} (rootPath={})", getClass().getSimpleName(), rootPath); } @Override public String removeRecursively(final String relativePath) throws Denied, NotFound { final File toRemove = resolve(relativePath); removeRecursively(toRemove); return uri(relativePath); } @Override public String makeDirectory(final String relativePath) throws Denied { final File toCreate = resolve(relativePath); try { toCreate.mkdirs(); return "file://" + toCreate.getCanonicalPath(); } catch (final Exception e) { throw new Denied(format("Error while creating '%s'", relativePath), e); } } @Override public Writer writer(final String relativePath) throws Denied { return new BufferedWriter(new OutputStreamWriter(outStream(relativePath), StreamTool.UTF8)); } @Override public Reader reader(final String relativePath) throws Denied, NotFound { return new BufferedReader(new InputStreamReader( inStream(relativePath), StreamTool.UTF8)); } @Override public String uri(final String relativePath) throws NotFound { try { return "file://" + resolve(relativePath).getCanonicalPath(); } catch (final Exception e) { throw new NotFound(format("Error while lookig up '%s'", relativePath), e); } } private void removeRecursively(final File file) throws Denied, NotFound { if (file.isDirectory()) { for (final File child : file.listFiles()) removeRecursively(child); } if (!file.delete()) throw new Denied(format("Failed to delete file '%s'", file.getAbsolutePath())); } private File resolve(final String relativePath) throws Denied { try { final File f = new File(rootDir, relativePath).getCanonicalFile(); final String path = f.getCanonicalPath(); if (!path.startsWith(rootPath)) throw new Denied(format( "Cannot resolve '%s': all paths must be local to the fs root.", relativePath)); return f; } catch (final IOException e) { throw new Denied(format("Could not resolve '%s' due to IO error.", relativePath), e); } } private final OutputStream outStream(final String relativePath) throws Denied { final File file = resolve(relativePath); if (!file.exists()) { try { file.createNewFile(); } catch (final IOException e) { throw new Denied(format("Could not create file '%s'", relativePath)); } } if (!file.canWrite()) { throw new Denied(format("Cannot write to '%s'", relativePath)); } try { return new FileOutputStream(file); } catch (FileNotFoundException e) { throw new Denied(format("Unexpected: File '%s' vanished.", relativePath)); } } private final InputStream inStream(final String relativePath) throws Denied, NotFound { final File file = resolve(relativePath); if (!file.canRead()) throw new Denied(format("Cannot read '%s'", relativePath)); try { return new FileInputStream(file); } catch (final FileNotFoundException e) { throw new NotFound(format("Cannot find '%s'", relativePath), e); } } }