cpcc.vvrte.services.VirtualVehicleMigratorImpl.java Source code

Java tutorial

Introduction

Here is the source code for cpcc.vvrte.services.VirtualVehicleMigratorImpl.java

Source

// This code is part of the CPCC-NG project.
//
// Copyright (c) 2013 Clemens Krainer <clemens.krainer@gmail.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

package cpcc.vvrte.services;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tapestry5.hibernate.HibernateSessionManager;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.json.JSONObject;
import org.slf4j.Logger;

import cpcc.com.services.CommunicationResponse;
import cpcc.com.services.CommunicationResponse.Status;
import cpcc.com.services.CommunicationService;
import cpcc.core.services.RealVehicleRepository;
import cpcc.core.services.jobs.JobService;
import cpcc.core.services.jobs.TimeService;
import cpcc.core.utils.PropertyUtils;
import cpcc.vvrte.base.VvRteConstants;
import cpcc.vvrte.entities.VirtualVehicle;
import cpcc.vvrte.entities.VirtualVehicleState;
import cpcc.vvrte.entities.VirtualVehicleStorage;
import cpcc.vvrte.services.db.VvRteRepository;

/**
 * VirtualVehicleMigratorImpl
 */
public class VirtualVehicleMigratorImpl implements VirtualVehicleMigrator {
    private static final String DATA_VV_PROPERTIES = "vv/vv.properties";
    private static final String DATA_VV_SOURCE_JS = "vv/vv-source.js";
    private static final String DATA_VV_CONTINUATION_JS = "vv/vv-continuation.js";

    private Logger logger;
    private HibernateSessionManager sessionManager;
    private VvRteRepository vvRepository;
    private VirtualVehicleLauncher launcher;
    private JobService jobService;
    private TimeService timeService;
    private RealVehicleRepository rvRepository;
    private CommunicationService com;
    private int chunkSize;

    /**
     * @param logger the application logger.
     * @param sessionManager the Hibernate session manager.
     * @param vvRepository the virtual vehicle repository.
     * @param launcher the virtual vehicle launcher.
     * @param jobService the job service.
     * @param timeService the time service.
     * @param rvRepository the real vehicle repository.
     * @param com the communication service.
     * @param chunkSize the migration chunk size.
     */
    public VirtualVehicleMigratorImpl(Logger logger, HibernateSessionManager sessionManager,
            VvRteRepository vvRepository, VirtualVehicleLauncher launcher, JobService jobService,
            TimeService timeService, RealVehicleRepository rvRepository, CommunicationService com,
            @Symbol(VvRteConstants.MIGRATION_CHUNK_SIZE) int chunkSize) {
        this.logger = logger;
        this.sessionManager = sessionManager;
        this.vvRepository = vvRepository;
        this.launcher = launcher;
        this.jobService = jobService;
        this.timeService = timeService;
        this.rvRepository = rvRepository;
        this.com = com;
        this.chunkSize = chunkSize;
    }

    /**
     * @param chunkSize the migration chunk size to set.
     */
    public void setChunkSize(int chunkSize) {
        this.chunkSize = chunkSize;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void initiateMigration(VirtualVehicle vehicle) {
        jobService.addJobIfNotExists(VvRteConstants.MIGRATION_JOB_QUEUE_NAME,
                String.format(VvRteConstants.MIGRATION_FORMAT_SEND, vehicle.getId()));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public byte[] findChunk(VirtualVehicle virtualVehicle, String lastStorageName, int chunkNumber)
            throws IOException, ArchiveException {
        String name = lastStorageName != null && lastStorageName.startsWith("storage/")
                ? lastStorageName.substring(8)
                : "";

        List<VirtualVehicleStorage> storageChunk = vvRepository
                .findStorageItemsByVirtualVehicle(virtualVehicle.getId(), name, chunkSize);

        boolean lastChunk = storageChunk.size() == 0 || storageChunk.size() < chunkSize;

        ArchiveStreamFactory factory = new ArchiveStreamFactory("UTF-8");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ArchiveOutputStream outStream = factory.createArchiveOutputStream("tar", baos);

        writeVirtualVehicleProperties(virtualVehicle, outStream, chunkNumber, lastChunk);
        if (chunkNumber == 0) {
            writeVirtualVehicleSourceCode(virtualVehicle, outStream, chunkNumber);
            writeVirtualVehicleContinuation(virtualVehicle, outStream, chunkNumber);
        }

        writeVirtualVehicleStorageChunk(virtualVehicle, outStream, chunkNumber, storageChunk);

        outStream.close();
        baos.close();

        if (lastChunk) {
            virtualVehicle.setState(VirtualVehicleState.MIGRATION_COMPLETED_SND);
        }

        return baos.toByteArray();
    }

    /**
     * @param virtualVehicle the virtual vehicle.
     * @param os the output stream to write to.
     * @param chunkNumber the chunk number.
     * @throws IOException thrown in case of errors.
     */
    private void writeVirtualVehicleProperties(VirtualVehicle virtualVehicle, ArchiveOutputStream os,
            int chunkNumber, boolean lastChunk) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Properties virtualVehicleProps = fillVirtualVehicleProps(virtualVehicle, lastChunk);
        virtualVehicleProps.store(baos, "Virtual Vehicle Properties");
        baos.close();

        byte[] propBytes = baos.toByteArray();

        TarArchiveEntry entry = new TarArchiveEntry(DATA_VV_PROPERTIES);
        entry.setModTime(new Date());
        entry.setSize(propBytes.length);
        entry.setIds(0, chunkNumber);
        entry.setNames("vvrte", "cpcc");

        os.putArchiveEntry(entry);
        os.write(propBytes);
        os.closeArchiveEntry();
    }

    /**
     * @param virtualVehicle the virtual vehicle.
     * @param os the output stream to write to.
     * @param chunkNumber the chunk number.
     * @throws IOException thrown in case of errors.
     */
    private void writeVirtualVehicleContinuation(VirtualVehicle virtualVehicle, ArchiveOutputStream os,
            int chunkNumber) throws IOException {
        byte[] continuation = virtualVehicle.getContinuation();

        if (continuation == null) {
            return;
        }

        TarArchiveEntry entry = new TarArchiveEntry(DATA_VV_CONTINUATION_JS);
        entry.setModTime(new Date());
        entry.setSize(continuation.length);
        entry.setIds(0, chunkNumber);
        entry.setNames("vvrte", "cpcc");

        os.putArchiveEntry(entry);
        os.write(continuation);
        os.closeArchiveEntry();
    }

    /**
     * @param virtualVehicle the virtual vehicle.
     * @param os the output stream to write to.
     * @param chunkNumber the chunk number.
     * @throws IOException thrown in case of errors.
     */
    private void writeVirtualVehicleSourceCode(VirtualVehicle virtualVehicle, ArchiveOutputStream os,
            int chunkNumber) throws IOException {
        if (virtualVehicle.getCode() == null) {
            return;
        }

        byte[] source = virtualVehicle.getCode().getBytes("UTF-8");

        TarArchiveEntry entry = new TarArchiveEntry(DATA_VV_SOURCE_JS);
        entry.setModTime(new Date());
        entry.setSize(source.length);
        entry.setIds(0, chunkNumber);
        entry.setNames("vvrte", "cpcc");

        os.putArchiveEntry(entry);
        os.write(source);
        os.closeArchiveEntry();
    }

    /**
     * @param os the output stream to write to.
     * @throws IOException thrown in case of errors.
     */
    private void writeVirtualVehicleStorageChunk(VirtualVehicle virtualVehicle, ArchiveOutputStream os,
            int chunkNumber, List<VirtualVehicleStorage> storageChunk) throws IOException {
        for (VirtualVehicleStorage se : storageChunk) {
            logger.debug("Writing storage entry '" + se.getName() + "'");

            byte[] content = se.getContentAsByteArray();
            TarArchiveEntry entry = new TarArchiveEntry("storage/" + se.getName());
            entry.setModTime(se.getModificationTime());
            entry.setSize(content.length);
            entry.setIds(se.getId(), chunkNumber);
            entry.setNames("vvrte", "cpcc");

            os.putArchiveEntry(entry);
            os.write(content);
            os.closeArchiveEntry();
        }
    }

    /**
     * @param virtualVehicle the virtual vehicle.
     * @param lastChunk true if this property object is going to be transferred in the last data chunk.
     * @return the virtual vehicle properties.
     */
    private static Properties fillVirtualVehicleProps(VirtualVehicle virtualVehicle, boolean lastChunk) {
        Properties props = new Properties();
        props.setProperty("name", virtualVehicle.getName());
        props.setProperty("uuid", virtualVehicle.getUuid());
        PropertyUtils.setProperty(props, "api.version", virtualVehicle.getApiVersion());
        PropertyUtils.setProperty(props, "state", virtualVehicle.getPreMigrationState());
        PropertyUtils.setProperty(props, "stateInfo", virtualVehicle.getStateInfo());
        PropertyUtils.setProperty(props, "chunk", virtualVehicle.getChunkNumber());

        if (virtualVehicle.getMigrationSource() != null) {
            PropertyUtils.setProperty(props, "migration.source", virtualVehicle.getMigrationSource().getName());
        }

        if (virtualVehicle.getStartTime() != null) {
            PropertyUtils.setProperty(props, "start.time", virtualVehicle.getStartTime().getTime());
        }

        if (virtualVehicle.getEndTime() != null) {
            PropertyUtils.setProperty(props, "end.time", virtualVehicle.getEndTime().getTime());
        }

        if (lastChunk) {
            PropertyUtils.setProperty(props, "last.chunk", Boolean.TRUE);
        }

        return props;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void storeChunk(InputStream inStream) throws ArchiveException, IOException {
        boolean lastChunk = false;
        String chunkName = "unknown-" + System.currentTimeMillis();
        ArchiveStreamFactory f = new ArchiveStreamFactory();
        ArchiveInputStream ais = f.createArchiveInputStream("tar", inStream);
        VirtualVehicleHolder virtualVehicleHolder = new VirtualVehicleHolder();

        for (TarArchiveEntry entry = (TarArchiveEntry) ais
                .getNextEntry(); entry != null; entry = (TarArchiveEntry) ais.getNextEntry()) {
            chunkName = entry.getName();

            if (chunkName.startsWith("vv/")) {
                lastChunk |= storeVirtualVehicleEntry(ais, entry, virtualVehicleHolder);
                logMigratedChunk(chunkName, virtualVehicleHolder.getVirtualVehicle(), lastChunk);
            } else if (chunkName.startsWith("storage/")) {
                storeStorageEntry(ais, entry, virtualVehicleHolder.getVirtualVehicle());
                logMigratedChunk(chunkName, virtualVehicleHolder.getVirtualVehicle(), lastChunk);
            }
            // TODO message queue
            else {
                throw new IOException("Can not store unknown type of entry " + chunkName);
            }
        }

        sessionManager.commit();

        VirtualVehicle vv = virtualVehicleHolder.getVirtualVehicle();

        String result = new JSONObject("uuid", vv.getUuid(), "chunk", vv.getChunkNumber()).toCompactString();

        CommunicationResponse response = com.transfer(vv.getMigrationSource(),
                VvRteConstants.MIGRATION_ACK_CONNECTOR,
                org.apache.commons.codec.binary.StringUtils.getBytesUtf8(result));

        if (response.getStatus() == Status.OK) {
            if (lastChunk) {
                VirtualVehicleState newState = VirtualVehicleState.VV_NO_CHANGE_AFTER_MIGRATION
                        .contains(vv.getPreMigrationState()) ? vv.getPreMigrationState()
                                : VirtualVehicleState.MIGRATION_COMPLETED_RCV;

                vv.setPreMigrationState(null);
                updateStateAndCommit(vv, newState, null);
                launcher.stateChange(vv.getId(), vv.getState());
            }
        } else {
            String content = new String(response.getContent(), "UTF-8");
            logger.error("Migration ACK failed! Virtual vehicle: " + vv.getName() + " (" + vv.getUuid() + ") "
                    + content);
            updateStateAndCommit(vv, VirtualVehicleState.MIGRATION_INTERRUPTED_RCV, content);
        }

    }

    /**
     * @param vv the virtual vehicle.
     * @param newState the new state to set.
     * @param stateInfo the state info to set.
     */
    private void updateStateAndCommit(VirtualVehicle vv, VirtualVehicleState newState, String stateInfo) {
        vv.setState(newState);
        if (newState != VirtualVehicleState.DEFECTIVE) {
            vv.setStateInfo(stateInfo);
        }
        vv.setUpdateTime(timeService.newDate());
        sessionManager.getSession().saveOrUpdate(vv);
        sessionManager.commit();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void queueChunk(InputStream inputStream) throws IOException {
        byte[] data = IOUtils.toByteArray(inputStream);
        if (data.length == 0) {
            throw new IOException("No data!");
        }
        jobService.addJobIfNotExists(VvRteConstants.MIGRATION_JOB_QUEUE_NAME, VvRteConstants.MIGRATION_RECEIVE,
                data);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void ackChunk(InputStream inputStream) throws IOException {
        byte[] data = IOUtils.toByteArray(inputStream);
        if (data.length == 0) {
            throw new IOException("No data!");
        }
        jobService.addJobIfNotExists(VvRteConstants.MIGRATION_JOB_QUEUE_NAME, VvRteConstants.MIGRATION_CONTINUE,
                data);
    }

    /**
     * @param chunkName the name of the migrated chunk.
     * @param virtualVehicleHolder the virtual vehicle.
     */
    private void logMigratedChunk(String chunkName, VirtualVehicle virtualVehicle, boolean lastChunk) {
        String name = virtualVehicle != null ? " name=" + virtualVehicle.getName() : "";
        logger.debug("Migration of " + chunkName + name + (lastChunk ? " (last)" : " (not last)"));
    }

    /**
     * @param inStream the input stream containing a virtual vehicle chunk.
     * @param entry the previously read archive entry.
     * @param virtualVehicleHolder the holder object for the virtual vehicle read.
     * @throws IOException thrown in case of errors.
     */
    private boolean storeVirtualVehicleEntry(InputStream inStream, TarArchiveEntry entry,
            VirtualVehicleHolder virtualVehicleHolder) throws IOException {
        boolean lastChunk = false;

        if (DATA_VV_PROPERTIES.equals(entry.getName())) {
            Properties props = new Properties();
            props.load(inStream);

            lastChunk |= Boolean.parseBoolean(props.getProperty("last.chunk", "false"));

            VirtualVehicle vv = vvRepository.findVirtualVehicleByUUID(props.getProperty("uuid"));
            if (vv == null) {
                vv = createVirtualVehicle(props);
            } else {
                updateVirtualVehicle(props, vv);
            }

            virtualVehicleHolder.setVirtualVehicle(vv);
            sessionManager.getSession().saveOrUpdate(vv);
        } else if (DATA_VV_CONTINUATION_JS.equals(entry.getName())) {
            byte[] continuation = IOUtils.toByteArray(inStream);
            virtualVehicleHolder.getVirtualVehicle().setContinuation(continuation);
            sessionManager.getSession().saveOrUpdate(virtualVehicleHolder.getVirtualVehicle());
        } else if (DATA_VV_SOURCE_JS.equals(entry.getName())) {
            byte[] source = IOUtils.toByteArray(inStream);
            virtualVehicleHolder.getVirtualVehicle().setCode(new String(source, "UTF-8"));
            sessionManager.getSession().saveOrUpdate(virtualVehicleHolder.getVirtualVehicle());
        } else {
            throw new IOException("Can not store unknown virtual vehicle entry " + entry.getName());
        }

        return lastChunk;
    }

    /**
     * @param props the virtual vehicle properties.
     * @param lastChunk true if this is the last migration chunk.
     * @param vv the virtual vehicle
     * @throws IOException thrown in case of errors.
     */
    private void updateVirtualVehicle(Properties props, VirtualVehicle vv) throws IOException {
        if (!VirtualVehicleState.VV_STATES_FOR_MIGRATION_RCV.contains(vv.getState())) {
            throw new IOException("Virtual vehicle " + vv.getName() + " (" + vv.getUuid() + ")" + " has not state "
                    + VirtualVehicleState.MIGRATING_RCV + " but " + vv.getState() + " props=" + props);
        }

        if (vv.getMigrationDestination() != null) {
            throw new IOException("Virtual vehicle " + vv.getName() + " (" + vv.getUuid() + ") "
                    + "is being migrated and can not be a migration target.");
        }

        logger.debug("pre-migration:  " + vv.getName() + " (" + vv.getUuid() + ") " + vv.getState());

        vv.setChunkNumber(Integer.parseInt(props.getProperty("chunk")));
        vv.setMigrationSource(rvRepository.findRealVehicleByName(props.getProperty("migration.source")));
        sessionManager.getSession().saveOrUpdate(vv);

        logger.debug("post-migration: " + vv.getName() + " (" + vv.getUuid() + ") " + vv.getState());
    }

    /**
     * @param props the virtual vehicle properties.
     * @return the created virtual vehicle
     */
    private VirtualVehicle createVirtualVehicle(Properties props) {
        VirtualVehicle vv = new VirtualVehicle();
        vv.setName(props.getProperty("name"));
        vv.setUuid(props.getProperty("uuid"));
        vv.setApiVersion(Integer.parseInt(props.getProperty("api.version")));
        vv.setState(VirtualVehicleState.MIGRATING_RCV);
        vv.setPreMigrationState(getVehicleState(props.getProperty("state")));
        vv.setStateInfo(props.getProperty("stateInfo"));

        String startTime = props.getProperty("start.time");
        if (startTime != null) {
            vv.setStartTime(new Date(Long.parseLong(startTime)));
        }

        String endTime = props.getProperty("end.time");
        if (endTime != null) {
            vv.setEndTime(new Date(Long.parseLong(endTime)));
        }

        vv.setChunkNumber(Integer.parseInt(props.getProperty("chunk")));
        vv.setMigrationDestination(null);
        vv.setMigrationSource(rvRepository.findRealVehicleByName(props.getProperty("migration.source")));
        vv.setUpdateTime(timeService.newDate());
        sessionManager.getSession().saveOrUpdate(vv);
        return vv;
    }

    /**
     * @param stateString the state as a {@code String}.
     * @return the state as an enumeration.
     */
    private VirtualVehicleState getVehicleState(String stateString) {
        return StringUtils.isBlank(stateString) ? null : VirtualVehicleState.valueOf(stateString);
    }

    /**
     * @param inStream the input stream containing a virtual vehicle chunk.
     * @param entry the previously read archive entry.
     * @param virtualVehicleHolder the holder object for the virtual vehicle read.
     * @throws IOException thrown in case of errors.
     */
    private void storeStorageEntry(InputStream inStream, TarArchiveEntry entry, VirtualVehicle virtualVehicle)
            throws IOException {
        String name = entry.getName().substring(8, entry.getName().length());
        VirtualVehicleStorage item = vvRepository.findStorageItemByVirtualVehicleAndName(virtualVehicle, name);

        if (item == null) {
            item = new VirtualVehicleStorage();
            item.setName(name);
            item.setVirtualVehicle(virtualVehicle);
        }

        item.setModificationTime(entry.getModTime());
        item.setContentAsByteArray(IOUtils.toByteArray(inStream));

        sessionManager.getSession().saveOrUpdate(item);
    }

    /**
     * VirtualVehicleHolder
     */
    private static class VirtualVehicleHolder {
        private VirtualVehicle virtualVehicle;

        /**
         * @return the virtual vehicle.
         */
        public VirtualVehicle getVirtualVehicle() {
            return virtualVehicle;
        }

        /**
         * @param virtualVehicle the virtual vehicle to set.
         */
        public void setVirtualVehicle(VirtualVehicle virtualVehicle) {
            this.virtualVehicle = virtualVehicle;
        }
    }
}