com.eviware.loadui.impl.conversion.ReferenceToFileConverter.java Source code

Java tutorial

Introduction

Here is the source code for com.eviware.loadui.impl.conversion.ReferenceToFileConverter.java

Source

/*
 * Copyright 2013 SmartBear Software
 * 
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the European Commission - subsequent
 * versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * 
 * http://ec.europa.eu/idabc/eupl
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the Licence for the specific language governing permissions and limitations
 * under the Licence.
 */
package com.eviware.loadui.impl.conversion;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter;

import com.eviware.loadui.LoadUI;
import com.eviware.loadui.api.addressable.AddressableRegistry;
import com.eviware.loadui.api.events.CollectionEvent;
import com.eviware.loadui.api.events.EventHandler;
import com.eviware.loadui.api.events.PropertyEvent;
import com.eviware.loadui.api.messaging.MessageEndpoint;
import com.eviware.loadui.api.messaging.MessageListener;
import com.eviware.loadui.api.model.PropertyHolder;
import com.eviware.loadui.impl.property.Reference;
import com.google.common.io.Closeables;

public class ReferenceToFileConverter implements Converter<Reference, File>, EventHandler<CollectionEvent> {
    public final static Logger log = LoggerFactory.getLogger(ReferenceToFileConverter.class);

    public final static String CHANNEL = "/" + ReferenceToFileConverter.class.getName();

    private final File storage = new File(System.getProperty(LoadUI.LOADUI_HOME) + File.separator + "fileStorage");
    private final FilePropertyListener filePropertyListener = new FilePropertyListener();
    private final Set<File> filesInUse = new HashSet<>();

    private final Map<String, File> files = new HashMap<>();
    private final FileReceiver listener = new FileReceiver();

    private final Set<String> filesInProgress = new HashSet<>();

    public ReferenceToFileConverter(AddressableRegistry addressableRegistry,
            ScheduledExecutorService executorService) {
        addressableRegistry.addEventListener(CollectionEvent.class, this);
        executorService.scheduleAtFixedRate(new RemoveOldFilesTask(), 5, 5, TimeUnit.MINUTES);

        if (!storage.isDirectory())
            if (!storage.mkdirs())
                throw new RuntimeException("Unable to create path: " + storage.getAbsolutePath());
    }

    @Override
    public File convert(Reference source) {
        if (source.getId().charAt(0) == ':')
            return new File(source.getId().substring(1));

        File target = getOrCreate(source);
        String hash = source.getId();

        synchronized (target) {
            while (!target.exists() || filesInProgress.contains(hash)) {
                try {
                    log.debug("waiting for {}", source.getId());
                    target.wait();
                } catch (InterruptedException e) {
                    log.debug("got waken up, was waiting for {}", source.getId());
                }
            }
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(target);
                String md5Hex = DigestUtils.md5Hex(fis);
                log.debug("target is: {} and is {} bytes with hash " + md5Hex, target, target.length());
            } catch (IOException e) {
                log.error("Exception while verifying hash", e);
                e.printStackTrace();
            } finally {
                Closeables.closeQuietly(fis);
            }
        }

        return target;
    }

    private File getOrCreate(Reference source) {
        String hash = source.getId();
        synchronized (files) {
            log.debug("getOrCreate() {}", hash);

            if (!files.containsKey(hash)) {
                files.put(hash, new File(storage, hash));
                log.debug("Adding {} to filesInProgress", hash);
                filesInProgress.add(hash);
                source.getEndpoint().addMessageListener(CHANNEL, listener);
                source.getEndpoint().sendMessage(FileToReferenceConverter.CHANNEL, hash);
            } else if (!filesInProgress.contains(hash) && !isFileHashValid(hash)) {
                log.error("File has been changed. Request file again...");
                log.debug("Removing {} from filesInProgress", hash);
                if (!files.get(hash).delete())
                    log.error("Unable to delete file: {}", files.get(hash));
                source.getEndpoint().addMessageListener(CHANNEL, listener);
                source.getEndpoint().sendMessage(FileToReferenceConverter.CHANNEL, hash);
            }
            return files.get(hash);
        }
    }

    private boolean isFileHashValid(String hash) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(files.get(hash));
            String md5Hex = DigestUtils.md5Hex(fis);
            return hash.equals(md5Hex);
        } catch (IOException e) {
            return false;
        } finally {
            Closeables.closeQuietly(fis);
        }
    }

    @Override
    public void handleEvent(CollectionEvent event) {
        Object element = event.getElement();
        if (element instanceof PropertyHolder) {
            PropertyHolder propertyHolder = (PropertyHolder) element;
            if (event.getEvent() == CollectionEvent.Event.ADDED)
                propertyHolder.addEventListener(PropertyEvent.class, filePropertyListener);
            else
                propertyHolder.removeEventListener(PropertyEvent.class, filePropertyListener);
        }
    }

    private class FileReceiver implements MessageListener {
        private final Map<String, OutputStream> writers = Collections
                .synchronizedMap(new HashMap<String, OutputStream>());

        @Override
        @SuppressWarnings("unchecked")
        public void handleMessage(String channel, MessageEndpoint endpoint, Object data) {
            Map<String, Object> map = (Map<String, Object>) data;
            for (Entry<String, Object> entry : map.entrySet()) {
                String hash = entry.getKey();
                synchronized (files) {
                    if (files.containsKey(hash)) {
                        File file = files.get(hash);
                        if (FileToReferenceConverter.START.equals(entry.getValue())) {
                            try {
                                if (!file.createNewFile())
                                    log.debug("Failed creating file: " + file.getAbsolutePath());
                                writers.put(hash, new FileOutputStream(file));
                            } catch (FileNotFoundException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                            //
                            catch (IOException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        } else if (FileToReferenceConverter.STOP.equals(entry.getValue())) {
                            Closeables.closeQuietly(writers.remove(hash));
                            synchronized (file) {
                                FileInputStream fis = null;
                                try {
                                    fis = new FileInputStream(file);
                                    String md5Hex = DigestUtils.md5Hex(fis);
                                    if (hash.equals(md5Hex)) {
                                        log.debug("File done. Removing {} from filesInProgress", hash);
                                        filesInProgress.remove(hash);
                                        file.notifyAll();
                                    } else {
                                        log.error("File transfered with MD5 hash: {}, should be {}. Retrying...",
                                                md5Hex, hash);

                                        if (!file.delete())
                                            log.error("Failed to delete file: " + file.getAbsolutePath());

                                        endpoint.sendMessage(FileToReferenceConverter.CHANNEL, hash);
                                    }
                                } catch (IOException e) {
                                    log.error("Exception while verifying completed file", e);
                                } finally {
                                    Closeables.closeQuietly(fis);
                                }
                            }
                        } else if (writers.containsKey(hash)) {
                            try {
                                writers.get(hash).write(Base64.decodeBase64((String) entry.getValue()));
                            } catch (IOException e) {
                                log.error("Exception while writing to file", e);
                            }
                        }
                    }
                }
            }
        }
    }

    private class FilePropertyListener implements EventHandler<PropertyEvent> {
        @Override
        public void handleEvent(PropertyEvent event) {
            if (File.class.isAssignableFrom(event.getProperty().getType())) {
                if (event.getEvent() == PropertyEvent.Event.CREATED)
                    filesInUse.add((File) event.getProperty().getValue());
                else if (event.getEvent() == PropertyEvent.Event.VALUE) {
                    filesInUse.remove(event.getPreviousValue());
                    filesInUse.add((File) event.getProperty().getValue());
                } else if (event.getEvent() == PropertyEvent.Event.DELETED)
                    filesInUse.remove(event.getPreviousValue());
            }
        }
    }

    private class RemoveOldFilesTask implements Runnable {
        // max storage size in MB
        public static final int MAX_STORAGE_SIZE = 100;

        private final Comparator<File> compareByLastUsed = new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                return (int) (o1.lastModified() - o2.lastModified());
            }
        };

        @Override
        public void run() {
            List<File> unused = new ArrayList<>();
            for (File file : storage.listFiles())
                if (!filesInUse.contains(file))
                    unused.add(file);

            Collections.sort(unused, compareByLastUsed);

            long limit = MAX_STORAGE_SIZE * 1024 * 1024;
            long size = 0;
            for (int i = unused.size() - 1; i >= 0; i--) {
                size += unused.get(i).length();
                if (size > limit) {
                    int newSize = unused.size() - i - 1;
                    while (unused.size() > newSize) {
                        final File file = unused.remove(0);
                        if (!file.delete())
                            log.error("Unable to remove file: {}", file);
                    }
                    break;
                }
            }
        }
    }
}