0

I'm not sure if that's the right way to ask this, but I'm gonna try to explain my case and what I need.

I have a big java project, that upload files in many different java classes, like too many, and I have around 7 different main folders where the files are uploaded. The files at the moment are saved inside the webapp context, and I need to save them outside of context.

If there were only a few classes that upload these files I could spend a few days changing every class and direct it to a path outisde of context, but there are way too many classes, so I have to figure out a way to do it without changing every class, or any class at all, which would be ideal.

Every upload is done in the following way:

I get real path of one of my main folders:

String realpath = httpServletRequest.getSession()
                                    .getServletContext()
                                    .getRealPath("/mainfolder1/mainsubfolder1/");

Then I get the file and set custom file name:

FormFile file = myForm.getFile();
String contentType = file.getContentType();
String fileName  = file.getFileName();
int fileSize     = file.getFileSize();
customFileName = "anyName" + fileName.substring(fileName.lastIndexOf("."));

Then I validate and save the file:

if (fileSize > 0 && contentType != null && fileName.length() > 0){
    InputStream in = file.getInputStream();
    OutputStream bos = new FileOutputStream(realpath + "/" + customFileName);

    int byteRead = 0;
    byte[] buffer = new byte[8192];
    while ((byteRead = in.read(buffer, 0, 8192)) != -1){
      bos.write(buffer, 0, byteRead);
    }
    bos.close();
    in.close();
}

Very simple way to save my files, and as you can see, they are saved inside context.

So if I could somehow override java.io.FileOutputStream, to not only save it inside context, but to make a copy outside of context too, that would be great, like save it in the specified path and also on some other path outside of context.

But I don't know if this is possible, or how to reproduce this behaviour.

What I need is to keep the class code exactly as it is but write the file 2 times:

First here: "/insideContext/mainfolder1/mainsubfolder1/"

Then here: "/outsideContext/mainfolder1/mainsubfolder1/"

Is this possible? If not, what would be the best way to accomplish this?

Kick Buttowski
  • 6,709
  • 13
  • 37
  • 58
Lauro182
  • 1,597
  • 3
  • 15
  • 41
  • 1
    What about extracting the "save" part into a dedicated method then refactor it to do what you want? –  Apr 16 '15 at 18:53

1 Answers1

2

I'd refactor and use Decorator or Wrapper pattern. More about it here

Below some simple idea you could use.

public class ContextAwareDuplicatorOutputStream extends OutputStream {

FileOutputStream insideContext;
FileOutputStream outsideContext;

public ContextAwareDuplicatorOutputStream(String insideContextPath,
        String outsideContextPath, String fileName)
        throws FileNotFoundException {
    insideContext = new FileOutputStream(insideContextPath
            + File.pathSeparator + fileName);
    outsideContext = new FileOutputStream(outsideContextPath
            + File.pathSeparator + fileName);
}

@Override
public void close() throws IOException {
    insideContext.close();
    outsideContext.close();
}

@Override
public void flush() throws IOException {
    insideContext.flush();
    outsideContext.flush();
}

@Override
public void write(byte[] b) throws IOException {
    insideContext.write(b);
    outsideContext.write(b);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
    insideContext.write(b, off, len);
    outsideContext.write(b, off, len);
}

@Override
public void write(int b) throws IOException {
    insideContext.write(b);
    outsideContext.write(b);
}

}

Since you don't want to edit anything on your code, create a ServletContextListener that monitor the folder where you upload, and on the new file event, you copy it to the proper directory. Here is awnsered how to monitor a directory. Directory listener in Java

Below here is a small code, not really perfect, but the idea is there

public class FileMonitorServletContextListener implements
        ServletContextListener {

    public interface FileMonitor {

        void start(String fromFolder, String toFolder);

        void stop();

    }

    public class SimpleThreadedWatcher implements FileMonitor {

        private class SimpleThread extends Thread {

            private boolean running = true;
            private String fromFolder;
            private String toFolder;

            public SimpleThread(String fromFolder, String toFolder) {
                this.fromFolder = fromFolder;
                this.toFolder = toFolder;
            }

            private void copy(Path child, String toFolder) {
                // Copy the file to the folder
            }

            @Override
            public void run() {
                try {
                    WatchService watcher = FileSystems.getDefault()
                            .newWatchService();
                    Path fromPath = Paths.get(fromFolder);
                    watcher = FileSystems.getDefault().newWatchService();

                    WatchKey key = fromPath.register(watcher,
                            StandardWatchEventKinds.ENTRY_CREATE);

                    while (running) {

                        for (WatchEvent<?> event : key.pollEvents()) {
                            // Context for directory entry event is the file
                            // name of
                            // entry
                            @SuppressWarnings("unchecked")
                            WatchEvent<Path> ev = (WatchEvent<Path>) event;

                            Path name = ev.context();
                            Path child = fromPath.resolve(name);

                            // print out event
                            System.out.format("%s: %s\n", event.kind().name(),
                                    child);

                            copy(child, toFolder);

                            boolean valid = key.reset();
                            if (!valid) {
                                break;
                            }
                        }

                        Thread.sleep(1000);
                    }
                } catch (Exception e) {
                    throw new RuntimeException("Error: ", e);
                }
            }

            public void stopWorking() {
                running = false;
            }

        }

        private SimpleThread worker;

        @Override
        public void start(String fromFolder, String toFolder) {
            worker = new SimpleThread(fromFolder, toFolder);
            worker.start();
        }

        @Override
        public void stop() {
            worker.stopWorking();
        }

    }

    FileMonitor fileMonitor = new SimpleThreadedWatcher();

    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        fileMonitor.stop();
    }

    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        fileMonitor.start("FROM", "TO");
    }

}
Community
  • 1
  • 1
André
  • 2,184
  • 1
  • 22
  • 30
  • Hey, your idea is exactly what I'm looking for, I wouldn't mind refactoring this, but how would you do it? Search and replace every "import java.io.OutputStream;" ? Or what would be your approach? – Lauro182 Apr 16 '15 at 19:17
  • Honestly, I would wrap this code into a Utility class or something, then I would rewrite the upload to use that new Utility class. Then next time you need to change the upload behavior, hey, just need to change this Utility class I also added a small code to watch a folder, you might want to try that first? It might have a issue with big files, the create event is dispatched before the file gets fully written in the disk. – André Apr 16 '15 at 19:39
  • Well, it is very testeable, but I'm going to try the watch folder first, the only thing is that I'm worried of the resource comsumption, since there are already too many files in the folders, if you open a folder it takes several minutes to load, but since it is event based it should not use that many resources. Thanks a lot for the help, I'll get back to you after testing. – Lauro182 Apr 16 '15 at 19:44
  • And yeah, I will make a new utility class for uploading files for the upcoming new classes, but at the moment I first need to change current behaviour changing the code as little as possible. – Lauro182 Apr 16 '15 at 19:59
  • From http://jnotify.sourceforge.net/linux.html: "Since Linux INotify API does not support recursive listening on a directory, JNotify add this functionality by createing an INotify watch on every sub directory under the watched directory (transparently). this process takes a time which is linear to the number of directories in the tree being recursively watched, and require system resources, namely - INotify watches, which are limited, by default to 8192 watches per processes." So, jNotify is not an option, and since the enviroment is running java 6, directory watch is not an option. – Lauro182 Apr 16 '15 at 22:22
  • 1
    Worked like a charm! I just adapted the idea to my necessities, and very small amount of work, thanks a lot! – Lauro182 Apr 20 '15 at 17:31