org.openremote.beehive.configuration.www.UsersAPI.java Source code

Java tutorial

Introduction

Here is the source code for org.openremote.beehive.configuration.www.UsersAPI.java

Source

/*
 *
 *  * OpenRemote, the Home of the Digital Home.
 *  * Copyright 2008-2015, OpenRemote Inc.
 *  *
 *  * See the contributors.txt file in the distribution for a
 *  * full listing of individual contributors.
 *  *
 *  * This program is free software: you can redistribute it and/or modify
 *  * it under the terms of the GNU Affero General Public License as
 *  * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *  *
 *  * You should have received a copy of the GNU Affero General Public License
 *  * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package org.openremote.beehive.configuration.www;

import org.apache.commons.io.FileUtils;

import org.apache.commons.io.IOUtils;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.ReleaseId;
import org.kie.api.builder.model.KieModuleModel;
import org.openremote.beehive.configuration.exception.NotFoundException;
import org.openremote.beehive.configuration.model.Account;
import org.openremote.beehive.configuration.model.Command;
import org.openremote.beehive.configuration.model.ControllerConfiguration;
import org.openremote.beehive.configuration.model.Device;
import org.openremote.beehive.configuration.model.ProtocolAttribute;
import org.openremote.beehive.configuration.model.RangeSensor;
import org.openremote.beehive.configuration.model.Sensor;
import org.openremote.beehive.configuration.model.SensorState;
import org.openremote.beehive.configuration.model.persistence.jpa.MinimalPersistentUser;
import org.openremote.beehive.configuration.repository.AccountRepository;
import org.openremote.beehive.configuration.repository.MinimalPersistentUserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.ServerErrorException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.StreamingOutput;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Component
@Path("/user")
@Scope("prototype")
public class UsersAPI {
    private final static String CONTROLLER_XSD_PATH = "/controller-2.0-M7.xsd";

    private final static String OPENREMOTE_NAMESPACE = "http://www.openremote.org";

    private static final Logger log = LoggerFactory.getLogger(UsersAPI.class);

    @Context
    private ResourceContext resourceContext;

    @Context
    private SecurityContext security;

    @Autowired
    AccountRepository accountRepository;

    @Autowired
    MinimalPersistentUserRepository userRepository;

    @GET
    @Produces("application/octet-stream")
    @Path("/{username}/openremote.zip")
    public Response getConfigurationFile(@PathParam("username") String username) {
        log.info("Get configuration for user " + username);

        MinimalPersistentUser user = userRepository.findByUsername(username);
        if (user == null) {
            log.error("Configuration requested for unknown user " + username);
            throw new NotFoundException();
        }

        Account account = accountRepository.findOne(user.getAccountId());
        if (account == null) {
            log.error("Account not found for user " + username);
            throw new NotFoundException();
        }

        java.nio.file.Path temporaryFolderForFinalCleanup = null;
        try {
            // Create temporary folder
            final java.nio.file.Path temporaryFolder = Files.createTempDirectory("OR");
            temporaryFolderForFinalCleanup = temporaryFolder;

            // Create panel.xml file
            final File panelXmlFile = createPanelXmlFile(temporaryFolder);

            // Create controller.xml file
            final File controllerXmlFile = createControllerXmlFile(temporaryFolder, account);

            // Create drools folder and rules file (rules/modeler_rules.drl)
            final File droolsFile = createRules(temporaryFolder, account);

            // Create and return openremote.zip file
            StreamingOutput stream = new StreamingOutput() {
                public void write(OutputStream output) throws IOException, WebApplicationException {
                    try {
                        ZipOutputStream zipOutput = new ZipOutputStream(output);
                        writeZipEntry(zipOutput, panelXmlFile, temporaryFolder);
                        writeZipEntry(zipOutput, controllerXmlFile, temporaryFolder);
                        if (droolsFile != null) {
                            writeZipEntry(zipOutput, droolsFile, temporaryFolder);
                        }
                        zipOutput.close();
                    } catch (Exception e) {
                        log.error("Impossible to stream openremote.zip file", e);
                        throw new WebApplicationException(e);
                    } finally {
                        removeTemporaryFiles(temporaryFolder);
                    }
                }
            };

            // We've been able to build everything we need, set this to null so the cleanup is done
            // after the streaming has been done and not before (in final block of this method)
            temporaryFolderForFinalCleanup = null;
            return Response.ok(stream).header("content-disposition", "attachment; filename = \"openremote.zip\"")
                    .build();
        } catch (IOException e) {
            log.error("Issue creating openremote.zip file", e);
        } finally {
            removeTemporaryFiles(temporaryFolderForFinalCleanup);
        }

        return Response.serverError().build();
    }

    private File createRules(java.nio.file.Path temporaryFolder, Account account) throws IOException {
        ControllerConfiguration rulesConfiguration = account.getControllerConfigurationByName("rules.editor");
        File rulesFolder = new File(temporaryFolder.toFile(), "rules");
        rulesFolder.mkdir(); // TODO test return value
        final File droolsFile = new File(rulesFolder, "modeler_rules.drl");

        FileOutputStream fos = new FileOutputStream(droolsFile);

        if (rulesConfiguration != null) {
            PrintWriter pw = new PrintWriter(fos);
            pw.print(rulesConfiguration.getValue());
            pw.close();
        }
        fos.close();

        KieServices ks = KieServices.Factory.get();

        KieFileSystem kfs = ks.newKieFileSystem();

        KieModuleModel kproj = ks.newKieModuleModel();
        kfs.writeKModuleXML(kproj.toXML());

        ReleaseId releaseId = ks.newReleaseId("org.openremote.controller", "rules", "1.0");
        kfs.generateAndWritePomXML(releaseId);

        kfs.write("src/main/resources/modeler_rules.drl", rulesConfiguration.getValue());

        KieBuilder kb = ks.newKieBuilder(kfs).buildAll();

        // TODO: check for errors
        if (kb.getResults().hasMessages(org.kie.api.builder.Message.Level.ERROR)) {
            for (org.kie.api.builder.Message result : kb.getResults().getMessages()) {
                System.out.println(result.getText());
                return null;
            }
        }

        final File kjarFile = new File(rulesFolder, "modeler_rules.kjar");

        InternalKieModule kieModule = (InternalKieModule) ks.getRepository().getKieModule(releaseId);
        FileUtils.writeByteArrayToFile(kjarFile, kieModule.getBytes());

        return kjarFile;
    }

    private void writeZipEntry(ZipOutputStream zipOutput, File file, java.nio.file.Path basePath)
            throws IOException {
        ZipEntry entry = new ZipEntry(basePath.relativize(file.toPath()).toString());
        entry.setSize(file.length());
        entry.setTime(file.lastModified());
        zipOutput.putNextEntry(entry);

        IOUtils.copy(new FileInputStream(file), zipOutput);

        zipOutput.flush();
        zipOutput.closeEntry();
    }

    // We just require an empty panel.xml file
    // Write bare minimum to it
    private File createPanelXmlFile(java.nio.file.Path temporaryFolder) throws IOException {
        File panelXmlFile = new File(temporaryFolder.toFile(), "panel.xml");

        FileOutputStream fos = new FileOutputStream(panelXmlFile);
        PrintWriter pw = new PrintWriter(fos);
        pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        pw.println(
                "<openremote xmlns=\"http://www.openremote.org\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.openremote.org http://www.openremote.org/schemas/panel.xsd\">");
        pw.println("</openremote>");
        pw.close();
        fos.close();

        return panelXmlFile;
    }

    private File createControllerXmlFile(java.nio.file.Path temporaryFolder, Account account) {
        File controllerXmlFile = new File(temporaryFolder.toFile(), "controller.xml");

        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

        try {
            documentBuilderFactory.setNamespaceAware(true);
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            DOMImplementation domImplementation = documentBuilder.getDOMImplementation();
            Document document = domImplementation.createDocument(OPENREMOTE_NAMESPACE, "openremote", null);
            document.getDocumentElement().setAttributeNS("http://www.w3.org/2001/XMLSchema-instance",
                    "xsi:schemaLocation",
                    "http://www.openremote.org http://www.openremote.org/schemas/controller.xsd");

            Element componentsElement = document.createElementNS(OPENREMOTE_NAMESPACE, "components");
            document.getDocumentElement().appendChild(componentsElement);
            writeSensors(document, document.getDocumentElement(), account, findHighestCommandId(account));
            writeCommands(document, document.getDocumentElement(), account);
            writeConfig(document, document.getDocumentElement(), account);

            // Document is fully built, validate against schema before writing to file
            URL xsdResource = UsersAPI.class.getResource(CONTROLLER_XSD_PATH);
            if (xsdResource == null) {
                log.error("Cannot find XSD schema ''{0}''. Disabling validation...", CONTROLLER_XSD_PATH);
            } else {
                String language = XMLConstants.W3C_XML_SCHEMA_NS_URI;
                SchemaFactory factory = SchemaFactory.newInstance(language);
                Schema schema = factory.newSchema(xsdResource);
                Validator validator = schema.newValidator();
                validator.validate(new DOMSource(document));
            }

            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            Result output = new StreamResult(controllerXmlFile);
            Source input = new DOMSource(document);
            transformer.transform(input, output);
        } catch (ParserConfigurationException e) {
            log.error("Error generating controller.xml file", e);
            throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR);
        } catch (TransformerConfigurationException e) {
            log.error("Error generating controller.xml file", e);
            throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR);
        } catch (TransformerException e) {
            log.error("Error generating controller.xml file", e);
            throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR);
        } catch (SAXException e) {
            log.error("Error generating controller.xml file", e);
            throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR);
        } catch (IOException e) {
            log.error("Error generating controller.xml file", e);
            throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR);
        }

        return controllerXmlFile;
    }

    private Long findHighestCommandId(Account account) {
        Long maxId = -1l;
        Collection<Device> devices = account.getDevices();

        for (Device device : devices) {
            Collection<Command> commands = device.getCommands();
            for (Command command : commands) {
                if (command.getId() > maxId) {
                    maxId = command.getId();
                }
            }
        }
        return maxId;
    }

    private void writeSensors(Document document, Element rootElement, Account account, Long offset) {
        Element sensorsElement = document.createElementNS(OPENREMOTE_NAMESPACE, "sensors");
        rootElement.appendChild(sensorsElement);

        Collection<Device> devices = account.getDevices();

        for (Device device : devices) {
            Collection<Sensor> sensors = device.getSensors();
            for (Sensor sensor : sensors) {
                Element sensorElement = document.createElementNS(OPENREMOTE_NAMESPACE, "sensor");
                sensorsElement.appendChild(sensorElement);
                sensorElement.setAttribute("id", Long.toString(sensor.getId() + offset));
                sensorElement.setAttribute("name", sensor.getName());
                sensorElement.setAttribute("type", sensor.getSensorType().toString().toLowerCase());

                Element includeElement = document.createElementNS(OPENREMOTE_NAMESPACE, "include");
                sensorElement.appendChild(includeElement);
                includeElement.setAttribute("type", "command");
                includeElement.setAttribute("ref",
                        Long.toString(sensor.getSensorCommandReference().getCommand().getId()));

                switch (sensor.getSensorType()) {
                case RANGE: {
                    Element minElement = document.createElementNS(OPENREMOTE_NAMESPACE, "min");
                    sensorElement.appendChild(minElement);
                    minElement.setAttribute("value", Integer.toString(((RangeSensor) sensor).getMinValue()));

                    Element maxElement = document.createElementNS(OPENREMOTE_NAMESPACE, "max");
                    sensorElement.appendChild(maxElement);
                    maxElement.setAttribute("value", Integer.toString(((RangeSensor) sensor).getMaxValue()));
                    break;
                }
                case SWITCH: {
                    Element stateElement = document.createElementNS(OPENREMOTE_NAMESPACE, "state");
                    sensorElement.appendChild(stateElement);
                    stateElement.setAttribute("name", "on");

                    stateElement = document.createElementNS(OPENREMOTE_NAMESPACE, "state");
                    sensorElement.appendChild(stateElement);
                    stateElement.setAttribute("name", "off");
                    break;
                }
                case CUSTOM: {
                    Collection<SensorState> states = sensor.getStates();
                    for (SensorState state : states) {
                        Element stateElement = document.createElementNS(OPENREMOTE_NAMESPACE, "state");
                        sensorElement.appendChild(stateElement);
                        stateElement.setAttribute("name", state.getName());
                        stateElement.setAttribute("value", state.getValue());
                    }
                    break;
                }
                }
            }
        }
    }

    private void writeCommands(Document document, Element rootElement, Account account) {
        Element commandsElement = document.createElementNS(OPENREMOTE_NAMESPACE, "commands");
        rootElement.appendChild(commandsElement);

        Collection<Device> devices = account.getDevices();

        for (Device device : devices) {
            Collection<Command> commands = device.getCommands();
            for (Command command : commands) {
                Element commandElement = document.createElementNS(OPENREMOTE_NAMESPACE, "command");
                commandsElement.appendChild(commandElement);
                commandElement.setAttribute("id", Long.toString(command.getId()));
                commandElement.setAttribute("protocol", command.getProtocol().getType());

                Collection<ProtocolAttribute> attributes = command.getProtocol().getAttributes();
                for (ProtocolAttribute attribute : attributes) {
                    Element propertyElement = document.createElementNS(OPENREMOTE_NAMESPACE, "property");
                    commandElement.appendChild(propertyElement);
                    propertyElement.setAttribute("name", attribute.getName());
                    propertyElement.setAttribute("value", attribute.getValue());
                }
                Element propertyElement = document.createElementNS(OPENREMOTE_NAMESPACE, "property");
                commandElement.appendChild(propertyElement);
                propertyElement.setAttribute("name", "name");
                propertyElement.setAttribute("value", command.getName());

                propertyElement = document.createElementNS(OPENREMOTE_NAMESPACE, "property");
                commandElement.appendChild(propertyElement);
                propertyElement.setAttribute("name", "urn:openremote:device-command:device-name");
                propertyElement.setAttribute("value", command.getDevice().getName());

                propertyElement = document.createElementNS(OPENREMOTE_NAMESPACE, "property");
                commandElement.appendChild(propertyElement);
                propertyElement.setAttribute("name", "urn:openremote:device-command:device-id");
                propertyElement.setAttribute("value", Long.toString(command.getDevice().getId()));
            }
        }
    }

    private void writeConfig(Document document, Element rootElement, Account account) {
        Element configElement = document.createElementNS(OPENREMOTE_NAMESPACE, "config");
        rootElement.appendChild(configElement);

        Collection<ControllerConfiguration> configurations = account.getControllerConfigurations();
        for (ControllerConfiguration configuration : configurations) {
            if (!"rules.editor".equals(configuration.getName())) {
                Element propertyElement = document.createElementNS(OPENREMOTE_NAMESPACE, "property");
                configElement.appendChild(propertyElement);
                propertyElement.setAttribute("name", configuration.getName());
                propertyElement.setAttribute("value", configuration.getValue());
            }
        }

    }

    private void removeTemporaryFiles(java.nio.file.Path directory) {
        if (directory == null) {
            return;
        }
        try {
            Files.walkFileTree(directory, new SimpleFileVisitor<java.nio.file.Path>() {
                @Override
                public FileVisitResult visitFile(java.nio.file.Path file, BasicFileAttributes attrs)
                        throws IOException {
                    Files.delete(file);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult postVisitDirectory(java.nio.file.Path dir, IOException exc)
                        throws IOException {
                    Files.delete(dir);
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            log.error("Could not clean-up temporary folder used to create openremote.zip file", e);
        }
    }

}