onl.netfishers.netshot.RestService.java Source code

Java tutorial

Introduction

Here is the source code for onl.netfishers.netshot.RestService.java

Source

/**
 * Copyright 2013-2016 Sylvain Cadilhac (NetFishers)
 * 
 * This file is part of Netshot.
 * 
 * Netshot 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 3 of the License, or
 * (at your option) any later version.
 * 
 * Netshot 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 Netshot.  If not, see <http://www.gnu.org/licenses/>.
 */
package onl.netfishers.netshot;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.UnknownHostException;
import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Priority;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Priorities;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.ExceptionMapper;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

import onl.netfishers.netshot.aaa.Radius;
import onl.netfishers.netshot.aaa.User;
import onl.netfishers.netshot.compliance.CheckResult;
import onl.netfishers.netshot.compliance.Exemption;
import onl.netfishers.netshot.compliance.HardwareRule;
import onl.netfishers.netshot.compliance.Policy;
import onl.netfishers.netshot.compliance.Rule;
import onl.netfishers.netshot.compliance.SoftwareRule;
import onl.netfishers.netshot.compliance.CheckResult.ResultOption;
import onl.netfishers.netshot.compliance.SoftwareRule.ConformanceLevel;
import onl.netfishers.netshot.compliance.rules.JavaScriptRule;
import onl.netfishers.netshot.compliance.rules.TextRule;
import onl.netfishers.netshot.device.Config;
import onl.netfishers.netshot.device.Device;
import onl.netfishers.netshot.device.DeviceDriver;
import onl.netfishers.netshot.device.DeviceGroup;
import onl.netfishers.netshot.device.Domain;
import onl.netfishers.netshot.device.DynamicDeviceGroup;
import onl.netfishers.netshot.device.Finder;
import onl.netfishers.netshot.device.Module;
import onl.netfishers.netshot.device.Network4Address;
import onl.netfishers.netshot.device.Network6Address;
import onl.netfishers.netshot.device.NetworkAddress;
import onl.netfishers.netshot.device.NetworkInterface;
import onl.netfishers.netshot.device.StaticDeviceGroup;
import onl.netfishers.netshot.device.Device.MissingDeviceDriverException;
import onl.netfishers.netshot.device.Device.Status;
import onl.netfishers.netshot.device.DeviceDriver.AttributeDefinition;
import onl.netfishers.netshot.device.Finder.Expression.FinderParseException;
import onl.netfishers.netshot.device.attribute.ConfigAttribute;
import onl.netfishers.netshot.device.attribute.ConfigLongTextAttribute;
import onl.netfishers.netshot.device.credentials.DeviceCliAccount;
import onl.netfishers.netshot.device.credentials.DeviceCredentialSet;
import onl.netfishers.netshot.device.credentials.DeviceSnmpCommunity;
import onl.netfishers.netshot.device.credentials.DeviceSshKeyAccount;
import onl.netfishers.netshot.work.Task;
import onl.netfishers.netshot.work.Task.ScheduleType;
import onl.netfishers.netshot.work.tasks.CheckComplianceTask;
import onl.netfishers.netshot.work.tasks.CheckGroupComplianceTask;
import onl.netfishers.netshot.work.tasks.CheckGroupSoftwareTask;
import onl.netfishers.netshot.work.tasks.DeviceJsScript;
import onl.netfishers.netshot.work.tasks.DiscoverDeviceTypeTask;
import onl.netfishers.netshot.work.tasks.PurgeDatabaseTask;
import onl.netfishers.netshot.work.tasks.RunDeviceGroupScriptTask;
import onl.netfishers.netshot.work.tasks.RunDeviceScriptTask;
import onl.netfishers.netshot.work.tasks.ScanSubnetsTask;
import onl.netfishers.netshot.work.tasks.TakeGroupSnapshotTask;
import onl.netfishers.netshot.work.tasks.TakeSnapshotTask;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.glassfish.grizzly.http.server.CLStaticHttpHandler;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.servlet.ServletRegistration;
import org.glassfish.grizzly.servlet.WebappContext;
import org.glassfish.grizzly.ssl.SSLContextConfigurator;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.servlet.ServletProperties;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.hibernate.transform.Transformers;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MarkerFactory;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

import difflib.Delta;
import difflib.DiffUtils;
import difflib.Patch;

/**
 * The RestService class exposes the Netshot methods as a REST service.
 */
@Path("/")
@DenyAll
public class RestService extends Thread {

    /** The HQL select query for "light" devices, to be prepended to the actual query. */
    private static final String DEVICELIST_BASEQUERY = "select d.id as id, d.name as name, d.family as family, d.mgmtAddress as mgmtAddress, d.status as status ";

    /** The logger. */
    private static Logger logger = LoggerFactory.getLogger(RestService.class);

    /** The static instance service. */
    private static RestService nsRestService;

    /**
     * Initializes the service.
     */
    public static void init() {
        nsRestService = new RestService();
        nsRestService.setUncaughtExceptionHandler(Netshot.exceptionHandler);
        nsRestService.start();
    }

    @Priority(Priorities.AUTHORIZATION)
    @PreMatching
    private static class SecurityFilter implements ContainerRequestFilter {

        @Context
        private HttpServletRequest httpRequest;

        @Inject
        javax.inject.Provider<UriInfo> uriInfo;

        @Override
        public void filter(ContainerRequestContext requestContext) throws IOException {
            User user = (User) httpRequest.getSession().getAttribute("user");
            requestContext.setSecurityContext(new Authorizer(user));
        }

        private class Authorizer implements SecurityContext {

            private User user;

            public Authorizer(User user) {
                this.user = user;
            }

            @Override
            public boolean isUserInRole(String role) {
                return (user != null && (("admin".equals(role) && user.getLevel() >= User.LEVEL_ADMIN)
                        || ("readwrite".equals(role) && user.getLevel() >= User.LEVEL_READWRITE)
                        || ("readonly".equals(role) && user.getLevel() >= User.LEVEL_READONLY)));
            }

            @Override
            public boolean isSecure() {
                return "https".equals(uriInfo.get().getRequestUri().getScheme());
            }

            @Override
            public Principal getUserPrincipal() {
                return user;
            }

            @Override
            public String getAuthenticationScheme() {
                return SecurityContext.FORM_AUTH;
            }
        }

    }

    public static class NetshotExceptionMapper implements ExceptionMapper<Throwable> {

        public Response toResponse(Throwable t) {
            if (!(t instanceof ForbiddenException)) {
                logger.error("Uncaught exception thrown by REST service", t);
            }
            if (t instanceof WebApplicationException) {
                return ((WebApplicationException) t).getResponse();
            } else {
                return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
            }
        }
    }

    public static class NetshotWebApplication extends ResourceConfig {
        public NetshotWebApplication() {
            registerClasses(RestService.class, SecurityFilter.class);
            register(NetshotExceptionMapper.class);
            register(RolesAllowedDynamicFeature.class);
            property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, "true");
            property(ServerProperties.APPLICATION_NAME, "Plumber");
            //property(ServerProperties.TRACING, "ALL");
            register(JacksonFeature.class);
        }
    }

    /**
     * Instantiates a new Netshot REST service.
     */
    public RestService() {
        this.setName("REST Service");
        httpStaticPath = Netshot.getConfig("netshot.http.staticpath", "/");
        httpApiPath = Netshot.getConfig("netshot.http.apipath", "/api");
        httpBaseUrl = Netshot.getConfig("netshot.http.baseurl", "http://localhost:8443");
        httpSslKeystoreFile = Netshot.getConfig("netshot.http.ssl.keystore.file", "netshot.jks");
        httpSslKeystorePass = Netshot.getConfig("netshot.http.ssl.keystore.pass", "netshotpass");
        httpBasePort = 8443;
        try {
            httpBasePort = Integer
                    .parseInt(Netshot.getConfig("netshot.http.baseport", Integer.toString(httpBasePort)));
        } catch (Exception e) {
            logger.warn("Unable to understand the HTTP base port configuration, using {}.", httpBasePort);
        }
    }

    private String httpStaticPath;
    private String httpApiPath;
    private String httpBaseUrl;
    private String httpSslKeystoreFile;
    private String httpSslKeystorePass;
    private int httpBasePort;

    /* (non-Javadoc)
     * @see java.lang.Thread#run()
     */
    public void run() {
        logger.info("Starting the Web/REST service thread.");
        try {

            SSLContextConfigurator sslContext = new SSLContextConfigurator();
            sslContext.setKeyStoreFile(httpSslKeystoreFile);
            sslContext.setKeyStorePass(httpSslKeystorePass);

            if (!sslContext.validateConfiguration(true)) {
                throw new RuntimeException("Invalid SSL settings for the embedded HTTPS server.");
            }
            SSLEngineConfigurator sslConfig = new SSLEngineConfigurator(sslContext).setClientMode(false)
                    .setNeedClientAuth(false).setWantClientAuth(false);
            URI url = UriBuilder.fromUri(httpBaseUrl).port(httpBasePort).build();
            HttpServer server = GrizzlyHttpServerFactory.createHttpServer(url, (GrizzlyHttpContainer) null, true,
                    sslConfig, false);

            WebappContext context = new WebappContext("GrizzlyContext", httpApiPath);
            ServletRegistration registration = context.addServlet("Jersey", ServletContainer.class);
            registration.setInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS,
                    NetshotWebApplication.class.getName());
            registration.addMapping(httpApiPath);
            context.deploy(server);
            HttpHandler staticHandler = new CLStaticHttpHandler(Netshot.class.getClassLoader(), "/www/");
            server.getServerConfiguration().addHttpHandler(staticHandler, httpStaticPath);

            server.start();

            synchronized (this) {
                while (true) {
                    this.wait();
                }
            }
        } catch (Exception e) {
            logger.error(MarkerFactory.getMarker("FATAL"), "Fatal error with the REST service.", e);
            throw new RuntimeException("Error with the REST service, see logs for more details.");
        }
    }

    /**
     * An error bean to be sent to the REST client.
     */
    @XmlRootElement(name = "error")
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsErrorBean {

        /** The error message. */
        private String errorMsg;

        /** The error code. */
        private int errorCode;

        /**
         * Instantiates a new error bean.
         */
        public RsErrorBean() {
        }

        /**
         * Instantiates a new error bean.
         *
         * @param errorMsg the error msg
         * @param errorCode the error code
         */
        public RsErrorBean(String errorMsg, int errorCode) {
            super();
            this.errorMsg = errorMsg;
            this.errorCode = errorCode;
        }

        /**
         * Gets the error message.msg
         *
         * @return the error message
         */
        @XmlElement
        public String getErrorMsg() {
            return errorMsg;
        }

        /**
         * Sets the error message.
         *
         * @param errorMsg the new error message
         */
        public void setErrorMsg(String errorMsg) {
            this.errorMsg = errorMsg;
        }

        /**
         * Gets the error code.
         *
         * @return the error code
         */
        @XmlElement
        public int getErrorCode() {
            return errorCode;
        }

        /**
         * Sets the error code.
         *
         * @param errorCode the new error code
         */
        public void setErrorCode(int errorCode) {
            this.errorCode = errorCode;
        }
    }

    /**
     * The NetshotBadRequestException class, a WebApplication exception
     * embedding an error message, to be sent to the REST client.
     */
    static public class NetshotBadRequestException extends WebApplicationException {

        /** The Constant NETSHOT_DATABASE_ACCESS_ERROR. */
        public static final int NETSHOT_DATABASE_ACCESS_ERROR = 20;

        /** The Constant NETSHOT_INVALID_IP_ADDRESS. */
        public static final int NETSHOT_INVALID_IP_ADDRESS = 100;

        /** The Constant NETSHOT_MALFORMED_IP_ADDRESS. */
        public static final int NETSHOT_MALFORMED_IP_ADDRESS = 101;

        /** The Constant NETSHOT_INVALID_DOMAIN. */
        public static final int NETSHOT_INVALID_DOMAIN = 110;

        /** The Constant NETSHOT_DUPLICATE_DOMAIN. */
        public static final int NETSHOT_DUPLICATE_DOMAIN = 111;

        /** The Constant NETSHOT_INVALID_DOMAIN_NAME. */
        public static final int NETSHOT_INVALID_DOMAIN_NAME = 112;

        /** The Constant NETSHOT_USED_DOMAIN. */
        public static final int NETSHOT_USED_DOMAIN = 113;

        /** The Constant NETSHOT_INVALID_TASK. */
        public static final int NETSHOT_INVALID_TASK = 120;

        /** The Constant NETSHOT_TASK_NOT_CANCELLABLE. */
        public static final int NETSHOT_TASK_NOT_CANCELLABLE = 121;

        /** The Constant NETSHOT_TASK_CANCEL_ERROR. */
        public static final int NETSHOT_TASK_CANCEL_ERROR = 122;

        /** The Constant NETSHOT_USED_CREDENTIALS. */
        public static final int NETSHOT_USED_CREDENTIALS = 130;

        /** The Constant NETSHOT_DUPLICATE_CREDENTIALS. */
        public static final int NETSHOT_DUPLICATE_CREDENTIALS = 131;

        /** The Constant NETSHOT_INVALID_CREDENTIALS_TYPE. */
        public static final int NETSHOT_INVALID_CREDENTIALS_TYPE = 132;

        /** The Constant NETSHOT_CREDENTIALS_NOTFOUND. */
        public static final int NETSHOT_CREDENTIALS_NOTFOUND = 133;

        /** The Constant NETSHOT_SCHEDULE_ERROR. */
        public static final int NETSHOT_SCHEDULE_ERROR = 30;

        /** The Constant NETSHOT_DUPLICATE_DEVICE. */
        public static final int NETSHOT_DUPLICATE_DEVICE = 140;

        /** The Constant NETSHOT_USED_DEVICE. */
        public static final int NETSHOT_USED_DEVICE = 141;

        /** The Constant NETSHOT_INVALID_DEVICE. */
        public static final int NETSHOT_INVALID_DEVICE = 142;

        /** The Constant NETSHOT_INVALID_CONFIG. */
        public static final int NETSHOT_INVALID_CONFIG = 143;

        /** The Constant NETSHOT_INCOMPATIBLE_CONFIGS. */
        public static final int NETSHOT_INCOMPATIBLE_CONFIGS = 144;

        /** The Constant NETSHOT_INVALID_DEVICE_CLASSNAME. */
        public static final int NETSHOT_INVALID_DEVICE_CLASSNAME = 150;

        /** The Constant NETSHOT_INVALID_SEARCH_STRING. */
        public static final int NETSHOT_INVALID_SEARCH_STRING = 151;

        /** The Constant NETSHOT_INVALID_GROUP_NAME. */
        public static final int NETSHOT_INVALID_GROUP_NAME = 160;

        /** The Constant NETSHOT_DUPLICATE_GROUP. */
        public static final int NETSHOT_DUPLICATE_GROUP = 161;

        /** The Constant NETSHOT_INCOMPATIBLE_GROUP_TYPE. */
        public static final int NETSHOT_INCOMPATIBLE_GROUP_TYPE = 162;

        /** The Constant NETSHOT_INVALID_DEVICE_IN_STATICGROUP. */
        public static final int NETSHOT_INVALID_DEVICE_IN_STATICGROUP = 163;

        /** The Constant NETSHOT_INVALID_GROUP. */
        public static final int NETSHOT_INVALID_GROUP = 164;

        /** The Constant NETSHOT_INVALID_DYNAMICGROUP_QUERY. */
        public static final int NETSHOT_INVALID_DYNAMICGROUP_QUERY = 165;

        /** The Constant NETSHOT_INVALID_SUBNET. */
        public static final int NETSHOT_INVALID_SUBNET = 170;

        /** The Constant NETSHOT_SCAN_SUBNET_TOO_BIG. */
        public static final int NETSHOT_SCAN_SUBNET_TOO_BIG = 171;

        /** The Constant NETSHOT_INVALID_POLICY_NAME. */
        public static final int NETSHOT_INVALID_POLICY_NAME = 180;

        /** The Constant NETSHOT_INVALID_POLICY. */
        public static final int NETSHOT_INVALID_POLICY = 181;

        /** The Constant NETSHOT_DUPLICATE_POLICY. */
        public static final int NETSHOT_DUPLICATE_POLICY = 182;

        /** The Constant NETSHOT_INVALID_RULE_NAME. */
        public static final int NETSHOT_INVALID_RULE_NAME = 190;

        /** The Constant NETSHOT_INVALID_RULE. */
        public static final int NETSHOT_INVALID_RULE = 191;

        /** The Constant NETSHOT_DUPLICATE_RULE. */
        public static final int NETSHOT_DUPLICATE_RULE = 192;

        /** The Constant NETSHOT_INVALID_USER. */
        public static final int NETSHOT_INVALID_USER = 200;

        /** The Constant NETSHOT_DUPLICATE_USER. */
        public static final int NETSHOT_DUPLICATE_USER = 201;

        /** The Constant NETSHOT_INVALID_USER_NAME. */
        public static final int NETSHOT_INVALID_USER_NAME = 202;

        /** The Constant NETSHOT_INVALID_PASSWORD. */
        public static final int NETSHOT_INVALID_PASSWORD = 203;

        public static final int NETSHOT_INVALID_SCRIPT = 220;

        public static final int NETSHOT_UNKNOWN_SCRIPT = 221;

        public static final int NETSHOT_DUPLICATE_SCRIPT = 222;

        /** The Constant serialVersionUID. */
        private static final long serialVersionUID = -4538169756895835186L;

        /**
         * Instantiates a new netshot bad request exception.
         *
         * @param message the message
         * @param errorCode the error code
         */
        public NetshotBadRequestException(String message, int errorCode) {
            super(Response.status(Response.Status.BAD_REQUEST).entity(new RsErrorBean(message, errorCode)).build());
        }
    }

    static public class NetshotNotAuthorizedException extends WebApplicationException {

        /** The Constant serialVersionUID. */
        private static final long serialVersionUID = -453816975689585686L;

        public NetshotNotAuthorizedException(String message, int errorCode) {
            super(Response.status(Response.Status.FORBIDDEN).entity(new RsErrorBean(message, errorCode)).build());
        }
    }

    /**
     * Gets the domains.
     *
     * @param request the request
     * @return the domains
     * @throws WebApplicationException the web application exception
     */
    @SuppressWarnings("unchecked")
    @GET
    @Path("domains")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsDomain> getDomains() throws WebApplicationException {
        logger.debug("REST request, domains.");
        Session session = Database.getSession();
        List<Domain> domains;
        try {
            domains = session.createCriteria(Domain.class).list();
            List<RsDomain> rsDomains = new ArrayList<RsDomain>();
            for (Domain domain : domains) {
                rsDomains.add(new RsDomain(domain));
            }
            return rsDomains;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the domains.", e);
            throw new NetshotBadRequestException("Unable to fetch the domains",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsDomain.
     */
    @XmlRootElement(name = "domain")
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsDomain {

        /** The id. */
        private long id = -1;

        /** The name. */
        private String name = "";

        /** The description. */
        private String description = "";

        /** The ip address. */
        private String ipAddress = "";

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Gets the name.
         *
         * @return the name
         */
        @XmlElement
        public String getName() {
            return name;
        }

        /**
         * Sets the name.
         *
         * @param name the new name
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * Gets the description.
         *
         * @return the description
         */
        @XmlElement
        public String getDescription() {
            return description;
        }

        /**
         * Sets the description.
         *
         * @param description the new description
         */
        public void setDescription(String description) {
            this.description = description;
        }

        /**
         * Gets the ip address.
         *
         * @return the ip address
         */
        @XmlElement
        public String getIpAddress() {
            return ipAddress;
        }

        /**
         * Sets the ip address.
         *
         * @param ipAddress the new ip address
         */
        public void setIpAddress(String ipAddress) {
            this.ipAddress = ipAddress;
        }

        /**
         * Instantiates a new rs domain.
         */
        public RsDomain() {

        }

        /**
         * Instantiates a new rs domain.
         *
         * @param domain the domain
         */
        public RsDomain(Domain domain) {
            this.id = domain.getId();
            this.name = domain.getName();
            this.description = domain.getDescription();
            this.ipAddress = domain.getServer4Address().getIp();
        }

    }

    /**
     * Adds the domain.
     *
     * @param request the request
     * @param newDomain the new domain
     * @return the rs domain
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("domains")
    @RolesAllowed("admin")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public RsDomain addDomain(RsDomain newDomain) throws WebApplicationException {
        logger.debug("REST request, add a domain");
        String name = newDomain.getName().trim();
        if (name.isEmpty()) {
            logger.warn("User posted an empty domain name.");
            throw new NetshotBadRequestException("Invalid domain name.",
                    NetshotBadRequestException.NETSHOT_INVALID_DOMAIN_NAME);
        }
        String description = newDomain.getDescription().trim();
        try {
            Network4Address v4Address = new Network4Address(newDomain.getIpAddress());
            Network6Address v6Address = new Network6Address("::");
            if (!v4Address.isNormalUnicast()) {
                logger.warn("User posted an invalid IP address.");
                throw new NetshotBadRequestException("Invalid IP address",
                        NetshotBadRequestException.NETSHOT_INVALID_IP_ADDRESS);
            }
            Domain domain = new Domain(name, description, v4Address, v6Address);
            Session session = Database.getSession();
            try {
                session.beginTransaction();
                session.save(domain);
                session.getTransaction().commit();
            } catch (HibernateException e) {
                session.getTransaction().rollback();
                logger.error("Error while adding a domain.", e);
                Throwable t = e.getCause();
                if (t != null && t.getMessage().contains("Duplicate entry")) {
                    throw new NetshotBadRequestException("A domain with this name already exists.",
                            NetshotBadRequestException.NETSHOT_DUPLICATE_DOMAIN);
                }
                throw new NetshotBadRequestException("Unable to add the domain to the database",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
            return new RsDomain(domain);
        } catch (UnknownHostException e) {
            logger.warn("User posted an invalid IP address.");
            throw new NetshotBadRequestException("Malformed IP address",
                    NetshotBadRequestException.NETSHOT_MALFORMED_IP_ADDRESS);
        }
    }

    /**
     * Sets the domain.
     *
     * @param request the request
     * @param id the id
     * @param rsDomain the rs domain
     * @return the rs domain
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("domains/{id}")
    @RolesAllowed("admin")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public RsDomain setDomain(@PathParam("id") Long id, RsDomain rsDomain) throws WebApplicationException {
        logger.debug("REST request, edit domain {}.", id);
        String name = rsDomain.getName().trim();
        if (name.isEmpty()) {
            logger.warn("User posted an invalid domain name.");
            throw new NetshotBadRequestException("Invalid domain name.",
                    NetshotBadRequestException.NETSHOT_INVALID_DOMAIN_NAME);
        }
        String description = rsDomain.getDescription().trim();
        Network4Address v4Address;
        try {
            v4Address = new Network4Address(rsDomain.getIpAddress());
            if (!v4Address.isNormalUnicast()) {
                logger.warn("User posted an invalid IP address");
                throw new NetshotBadRequestException("Invalid IP address",
                        NetshotBadRequestException.NETSHOT_INVALID_IP_ADDRESS);
            }
        } catch (UnknownHostException e) {
            logger.warn("Invalid IP address.", e);
            throw new NetshotBadRequestException("Malformed IP address",
                    NetshotBadRequestException.NETSHOT_MALFORMED_IP_ADDRESS);
        }
        Session session = Database.getSession();
        Domain domain;
        try {
            session.beginTransaction();
            domain = (Domain) session.load(Domain.class, id);
            domain.setName(name);
            domain.setDescription(description);
            domain.setServer4Address(v4Address);
            session.update(domain);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The domain doesn't exist.", e);
            throw new NetshotBadRequestException("The domain doesn't exist.",
                    NetshotBadRequestException.NETSHOT_INVALID_DOMAIN);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while editing the domain.", e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A domain with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_DOMAIN);
            }
            throw new NetshotBadRequestException("Unable to save the domain... is the name already in use?",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
        return new RsDomain(domain);
    }

    /**
     * Delete a domain.
     *
     * @param request the request
     * @param id the id
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("domains/{id}")
    @RolesAllowed("admin")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deleteDomain(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete domain {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            Domain domain = (Domain) session.load(Domain.class, id);
            session.delete(domain);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The domain doesn't exist.");
            throw new NetshotBadRequestException("The domain doesn't exist.",
                    NetshotBadRequestException.NETSHOT_INVALID_DOMAIN);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("foreign key constraint fails")) {
                throw new NetshotBadRequestException(
                        "Unable to delete the domain, there must be devices or tasks using it.",
                        NetshotBadRequestException.NETSHOT_USED_DOMAIN);
            }
            throw new NetshotBadRequestException("Unable to delete the domain",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the device interfaces.
     *
     * @param request the request
     * @param id the id
     * @return the device interfaces
     * @throws WebApplicationException the web application exception
     */
    @SuppressWarnings("unchecked")
    @GET
    @Path("devices/{id}/interfaces")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<NetworkInterface> getDeviceInterfaces(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, get device {} interfaces.", id);
        Session session = Database.getSession();
        try {
            List<NetworkInterface> deviceInterfaces;
            deviceInterfaces = session
                    .createQuery("from NetworkInterface AS networkInterface "
                            + "left join fetch networkInterface.ip4Addresses "
                            + "left join fetch networkInterface.ip6Addresses " + "where device = :device")
                    .setLong("device", id).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
            return deviceInterfaces;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the interfaces.", e);
            throw new NetshotBadRequestException("Unable to fetch the interfaces",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the device modules.
     *
     * @param request the request
     * @param id the id
     * @return the device modules
     * @throws WebApplicationException the web application exception
     */
    @SuppressWarnings("unchecked")
    @GET
    @Path("devices/{id}/modules")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<Module> getDeviceModules(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, get device {} modules.", id);
        Session session = Database.getSession();
        try {
            List<Module> deviceModules = session.createQuery("from Module m where device = :device")
                    .setLong("device", id).list();
            return deviceModules;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the modules.", e);
            throw new NetshotBadRequestException("Unable to fetch the modules",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the device last 20 tasks.
     *
     * @param request the request
     * @param id the id
     * @return the device tasks
     * @throws WebApplicationException the web application exception
     */
    @SuppressWarnings("unchecked")
    @GET
    @Path("devices/{id}/tasks")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<Task> getDeviceTasks(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, get device {} tasks.", id);
        Session session = Database.getSession();
        try {
            final int max = 20;
            final Class<?>[] taskTypes = new Class<?>[] { CheckComplianceTask.class, DiscoverDeviceTypeTask.class,
                    TakeSnapshotTask.class, RunDeviceScriptTask.class };
            final Criterion[] restrictions = new Criterion[] { Restrictions.eq("t.device.id", id),
                    Restrictions.eq("t.deviceId", id), Restrictions.eq("t.device.id", id),
                    Restrictions.eq("t.device.id", id) };
            List<Task> tasks = new ArrayList<Task>();
            for (int i = 0; i < taskTypes.length; i++) {
                List<Task> typeTasks = session.createCriteria(taskTypes[i], "t").add(restrictions[i]).list();
                tasks.addAll(typeTasks);
            }
            Collections.sort(tasks, new Comparator<Task>() {
                private int getPriority(Task.Status status) {
                    switch (status) {
                    case RUNNING:
                        return 1;
                    case WAITING:
                        return 2;
                    case SCHEDULED:
                        return 3;
                    case NEW:
                        return 4;
                    default:
                        return 10;
                    }
                }

                private Date getSignificantDate(Task t) {
                    if (t.getExecutionDate() == null) {
                        return t.getChangeDate();
                    } else {
                        return t.getExecutionDate();
                    }
                }

                @Override
                public int compare(Task o1, Task o2) {
                    int statusDiff = Integer.compare(this.getPriority(o1.getStatus()),
                            this.getPriority(o2.getStatus()));
                    if (statusDiff == 0) {
                        Date d1 = this.getSignificantDate(o1);
                        Date d2 = this.getSignificantDate(o2);
                        if (d1 == null) {
                            if (d2 == null) {
                                return 0;
                            } else {
                                return -1;
                            }
                        } else {
                            if (d2 == null) {
                                return 1;
                            } else {
                                return d2.compareTo(d1);
                            }
                        }
                    }
                    return statusDiff;
                }
            });
            return tasks.subList(0, (max > tasks.size() ? tasks.size() : max));
        } catch (Exception e) {
            logger.error("Unable to fetch the tasks.", e);
            throw new NetshotBadRequestException("Unable to fetch the tasks",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the device configs.
     *
     * @param request the request
     * @param id the id
     * @return the device configs
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("devices/{id}/configs")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<Config> getDeviceConfigs(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, get device {} configs.", id);
        Session session = Database.getSession();
        try {
            session.enableFilter("lightAttributesOnly");
            @SuppressWarnings("unchecked")
            List<Config> deviceConfigs = session
                    .createQuery("from Config c left join fetch c.attributes ca where c.device = :device")
                    .setLong("device", id).list();
            return deviceConfigs;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the configs.", e);
            throw new NetshotBadRequestException("Unable to fetch the configs",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the device config plain.
     *
     * @param request the request
     * @param id the id
     * @param item the item
     * @return the device config plain
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("configs/{id}/{item}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_OCTET_STREAM })
    public Response getDeviceConfigPlain(@PathParam("id") Long id, @PathParam("item") String item)
            throws WebApplicationException {
        logger.debug("REST request, get device {id} config {}.", id, item);
        Session session = Database.getSession();
        try {
            Config config = (Config) session.get(Config.class, id);
            if (config == null) {
                logger.warn("Unable to find the config object.");
                throw new WebApplicationException("Unable to find the configuration set",
                        javax.ws.rs.core.Response.Status.NOT_FOUND);
            }
            String text = null;
            for (ConfigAttribute attribute : config.getAttributes()) {
                if (attribute.getName().equals(item) && attribute instanceof ConfigLongTextAttribute) {
                    text = ((ConfigLongTextAttribute) attribute).getLongText().getText();
                    break;
                }
            }
            if (text == null) {
                throw new WebApplicationException("Configuration item not available",
                        javax.ws.rs.core.Response.Status.BAD_REQUEST);
            }
            String fileName = "config.cfg";
            try {
                SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm");
                fileName = String.format("%s_%s.cfg", config.getDevice().getName(),
                        formatter.format(config.getChangeDate()));
            } catch (Exception e) {
            }
            return Response.ok(text).header("Content-Disposition", "attachment; filename=" + fileName).build();
        } catch (HibernateException e) {
            throw new WebApplicationException("Unable to get the configuration",
                    javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsConfigDiff.
     */
    @XmlRootElement
    public static class RsConfigDiff {

        /** The original date. */
        private Date originalDate;

        /** The revised date. */
        private Date revisedDate;

        /** The deltas. */
        private Map<String, List<RsConfigDelta>> deltas = new HashMap<String, List<RsConfigDelta>>();

        /**
         * Instantiates a new rs config diff.
         *
         * @param originalDate the original date
         * @param revisedDate the revised date
         */
        public RsConfigDiff(Date originalDate, Date revisedDate) {
            this.originalDate = originalDate;
            this.revisedDate = revisedDate;
        }

        /**
         * Adds the delta.
         *
         * @param item the item
         * @param delta the delta
         */
        public void addDelta(String item, RsConfigDelta delta) {
            if (!deltas.containsKey(item)) {
                deltas.put(item, new ArrayList<RsConfigDelta>());
            }
            deltas.get(item).add(delta);
        }

        /**
         * Gets the original date.
         *
         * @return the original date
         */
        @XmlElement
        public Date getOriginalDate() {
            return originalDate;
        }

        /**
         * Gets the revised date.
         *
         * @return the revised date
         */
        @XmlElement
        public Date getRevisedDate() {
            return revisedDate;
        }

        /**
         * Gets the deltas.
         *
         * @return the deltas
         */
        @XmlElement
        public Map<String, List<RsConfigDelta>> getDeltas() {
            return deltas;
        }
    }

    /**
     * The Class RsConfigDelta.
     */
    @XmlRootElement
    public static class RsConfigDelta {

        /**
         * The Enum Type.
         */
        public static enum Type {

            /** The change. */
            CHANGE,

            /** The delete. */
            DELETE,

            /** The insert. */
            INSERT;
        }

        /** The item. */
        private String item;

        /** The diff type. */
        private Type diffType;

        /** The original position. */
        private int originalPosition;

        /** The revised position. */
        private int revisedPosition;

        /** The original lines. */
        private List<String> originalLines;

        /** The revised lines. */
        private List<String> revisedLines;

        /** The pre context. */
        private List<String> preContext;

        /** The post context. */
        private List<String> postContext;

        /**
         * Instantiates a new rs config delta.
         *
         * @param delta the delta
         * @param context the context
         */
        public RsConfigDelta(Delta<String> delta, List<String> context) {
            switch (delta.getType()) {
            case INSERT:
                this.diffType = Type.INSERT;
                break;
            case DELETE:
                this.diffType = Type.DELETE;
                break;
            case CHANGE:
            default:
                this.diffType = Type.CHANGE;
            }
            this.originalPosition = delta.getOriginal().getPosition();
            this.originalLines = delta.getOriginal().getLines();
            this.revisedPosition = delta.getRevised().getPosition();
            this.revisedLines = delta.getRevised().getLines();
            this.preContext = context.subList(Math.max(this.originalPosition - 3, 0), this.originalPosition);
            this.postContext = context.subList(
                    Math.min(this.originalPosition + this.originalLines.size(), context.size() - 1),
                    Math.min(this.originalPosition + this.originalLines.size() + 3, context.size() - 1));
        }

        /**
         * Gets the diff type.
         *
         * @return the diff type
         */
        @XmlElement
        public Type getDiffType() {
            return diffType;
        }

        /**
         * Gets the original position.
         *
         * @return the original position
         */
        @XmlElement
        public int getOriginalPosition() {
            return originalPosition;
        }

        /**
         * Gets the revised position.
         *
         * @return the revised position
         */
        @XmlElement
        public int getRevisedPosition() {
            return revisedPosition;
        }

        /**
         * Gets the original lines.
         *
         * @return the original lines
         */
        @XmlElement
        public List<String> getOriginalLines() {
            return originalLines;
        }

        /**
         * Gets the revised lines.
         *
         * @return the revised lines
         */
        @XmlElement
        public List<String> getRevisedLines() {
            return revisedLines;
        }

        /**
         * Gets the item.
         *
         * @return the item
         */
        @XmlElement
        public String getItem() {
            return item;
        }

        /**
         * Gets the pre context.
         *
         * @return the pre context
         */
        @XmlElement
        public List<String> getPreContext() {
            return preContext;
        }

        /**
         * Gets the post context.
         *
         * @return the post context
         */
        @XmlElement
        public List<String> getPostContext() {
            return postContext;
        }
    }

    /**
     * Gets the device config diff.
     *
     * @param request the request
     * @param id1 the id1
     * @param id2 the id2
     * @return the device config diff
     */
    @GET
    @Path("configs/{id1}/vs/{id2}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON })
    public RsConfigDiff getDeviceConfigDiff(@PathParam("id1") Long id1, @PathParam("id2") Long id2) {
        logger.debug("REST request, get device config diff, id {} and {}.", id1, id2);
        RsConfigDiff configDiffs;
        Session session = Database.getSession();
        Config config1 = null;
        Config config2 = null;
        try {
            config2 = (Config) session.get(Config.class, id2);
            if (config2 != null && id1 == 0) {
                config1 = (Config) session.createQuery(
                        "from Config c where c.device = :device and c.changeDate < :date2 order by c.changeDate desc")
                        .setEntity("device", config2.getDevice()).setTimestamp("date2", config2.getChangeDate())
                        .setMaxResults(1).uniqueResult();
                if (config1 == null) {
                    config1 = new Config(config2.getDevice());
                }
            } else {
                config1 = (Config) session.get(Config.class, id1);
            }
            if (config1 == null || config2 == null) {
                logger.error("Non existing config, {} or {}.", id1, id2);
                throw new NetshotBadRequestException("Unable to fetch the configs",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            }
            DeviceDriver driver1;
            DeviceDriver driver2;
            try {
                driver1 = config1.getDevice().getDeviceDriver();
                driver2 = config2.getDevice().getDeviceDriver();
            } catch (MissingDeviceDriverException e) {
                logger.error("Missing driver.");
                throw new NetshotBadRequestException("Missing driver",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            }
            if (!driver1.equals(driver2)) {
                logger.error("Incompatible configurations, {} and {} (different drivers).", id1, id2);
                throw new NetshotBadRequestException("Incompatible configurations",
                        NetshotBadRequestException.NETSHOT_INCOMPATIBLE_CONFIGS);
            }

            configDiffs = new RsConfigDiff(config1.getChangeDate(), config2.getChangeDate());
            Map<String, ConfigAttribute> attributes1 = config1.getAttributeMap();
            Map<String, ConfigAttribute> attributes2 = config2.getAttributeMap();
            for (AttributeDefinition definition : driver1.getAttributes()) {
                if (definition.isComparable()) {
                    ConfigAttribute attribute1 = attributes1.get(definition.getName());
                    ConfigAttribute attribute2 = attributes2.get(definition.getName());
                    String text1 = (attribute1 == null ? "" : attribute1.getAsText());
                    String text2 = (attribute2 == null ? "" : attribute2.getAsText());
                    List<String> lines1 = Arrays.asList(text1.replace("\r", "").split("\n"));
                    List<String> lines2 = Arrays.asList(text2.replace("\r", "").split("\n"));
                    Patch<String> patch = DiffUtils.diff(lines1, lines2);
                    for (Delta<String> delta : patch.getDeltas()) {
                        configDiffs.addDelta(definition.getTitle(), new RsConfigDelta(delta, lines1));
                    }
                }
            }
            return configDiffs;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the configs", e);
            throw new NetshotBadRequestException("Unable to fetch the configs",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the device.
     *
     * @param request the request
     * @param id the id
     * @return the device
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("devices/{id}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Device getDevice(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, device {}.", id);
        Session session = Database.getSession();
        Device device;
        try {
            device = (Device) session.createQuery(
                    "from Device d left join fetch d.credentialSets cs left join fetch d.ownerGroups g left join fetch d.complianceCheckResults left join fetch d.attributes where d.id = :id")
                    .setLong("id", id).uniqueResult();
            if (device == null) {
                throw new NetshotBadRequestException("Can't find this device",
                        NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
            }
            device.setMgmtDomain(Database.unproxy(device.getMgmtDomain()));
            device.setEolModule(Database.unproxy(device.getEolModule()));
            device.setEosModule(Database.unproxy(device.getEosModule()));
        } catch (HibernateException e) {
            logger.error("Unable to fetch the device", e);
            throw new NetshotBadRequestException("Unable to fetch the device",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
        return device;
    }

    /**
     * The Class RsLightDevice.
     */
    @XmlRootElement
    @XmlAccessorType(value = XmlAccessType.NONE)
    public static class RsLightDevice {

        /** The id. */
        private long id;

        /** The name. */
        private String name;

        /** The family. */
        private String family;

        /** The mgmt address. */
        private Network4Address mgmtAddress;

        /** The status. */
        private Device.Status status;

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Gets the name.
         *
         * @return the name
         */
        @XmlElement
        public String getName() {
            return name;
        }

        /**
         * Sets the name.
         *
         * @param name the new name
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * Gets the family.
         *
         * @return the family
         */
        @XmlElement
        public String getFamily() {
            return family;
        }

        /**
         * Sets the family.
         *
         * @param family the new family
         */
        public void setFamily(String family) {
            this.family = family;
        }

        /**
         * Gets the mgmt address.
         *
         * @return the mgmt address
         */
        @XmlElement
        public Network4Address getMgmtAddress() {
            return mgmtAddress;
        }

        /**
         * Sets the mgmt address.
         *
         * @param mgmtAddress the new mgmt address
         */
        public void setMgmtAddress(Network4Address mgmtAddress) {
            this.mgmtAddress = mgmtAddress;
        }

        /**
         * Gets the status.
         *
         * @return the status
         */
        @XmlElement
        public Device.Status getStatus() {
            return status;
        }

        /**
         * Sets the status.
         *
         * @param status the new status
         */
        public void setStatus(Device.Status status) {
            this.status = status;
        }

    }

    /**
     * Gets the devices.
     *
     * @param request the request
     * @return the devices
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("devices")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsLightDevice> getDevices() throws WebApplicationException {
        logger.debug("REST request, devices.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsLightDevice> devices = session.createQuery(DEVICELIST_BASEQUERY + "from Device d")
                    .setResultTransformer(Transformers.aliasToBean(RsLightDevice.class)).list();
            return devices;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the devices", e);
            throw new NetshotBadRequestException("Unable to fetch the devices",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the device types.
     *
     * @param request the request
     * @return the device types
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("devicetypes")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<DeviceDriver> getDeviceTypes() throws WebApplicationException {
        logger.debug("REST request, device types.");
        List<DeviceDriver> deviceTypes = new ArrayList<DeviceDriver>();
        deviceTypes.addAll(DeviceDriver.getAllDrivers());
        return deviceTypes;
    }

    @GET
    @Path("refresheddevicetypes")
    @RolesAllowed("admin")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<DeviceDriver> getDeviceTypesAndRefresh() throws WebApplicationException {
        logger.debug("REST request, refresh and get device types.");
        try {
            DeviceDriver.refreshDrivers();
        } catch (Exception e) {
            logger.error("Error in REST service while refreshing the device types.", e);
        }
        return this.getDeviceTypes();
    }

    /**
     * The Class RsDeviceFamily.
     */
    @XmlRootElement(name = "deviceType")
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsDeviceFamily {

        /** The device type. */
        private String driver;

        /** The device family. */
        private String deviceFamily;

        @XmlElement
        public String getDriver() {
            return driver;
        }

        public void setDriver(String driver) {
            this.driver = driver;
        }

        /**
         * Gets the device family.
         *
         * @return the device family
         */
        @XmlElement
        public String getDeviceFamily() {
            return deviceFamily;
        }

        /**
         * Sets the device family.
         *
         * @param deviceFamily the new device family
         */
        public void setDeviceFamily(String deviceFamily) {
            this.deviceFamily = deviceFamily;
        }
    }

    /**
     * Gets the device families.
     *
     * @param request the request
     * @return the device families
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("devicefamilies")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsDeviceFamily> getDeviceFamilies() throws WebApplicationException {
        logger.debug("REST request, device families.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsDeviceFamily> deviceFamilies = session
                    .createQuery("select distinct d.driver as driver, d.family as deviceFamily from Device d")
                    .setResultTransformer(Transformers.aliasToBean(RsDeviceFamily.class)).list();
            return deviceFamilies;
        } catch (HibernateException e) {
            logger.error("Error while loading device families.", e);
            throw new NetshotBadRequestException("Database error",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    @XmlRootElement(name = "partNumber")
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsPartNumber {
        private String partNumber;

        @XmlElement
        public String getPartNumber() {
            return partNumber;
        }

        public void setPartNumber(String partNumber) {
            this.partNumber = partNumber;
        }
    }

    /**
     * Gets the known part numbers.
     *
     * @param request the request
     * @return the part numbers
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("partnumbers")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsPartNumber> getPartNumbers() throws WebApplicationException {
        logger.debug("REST request, dpart numbers.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsPartNumber> partNumbers = session
                    .createQuery("select distinct m.partNumber as partNumber from Module m")
                    .setResultTransformer(Transformers.aliasToBean(RsPartNumber.class)).list();
            return partNumbers;
        } catch (HibernateException e) {
            logger.error("Error while loading part numbers.", e);
            throw new NetshotBadRequestException("Database error",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsNewDevice.
     */
    @XmlRootElement(name = "device")
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsNewDevice {

        /** The auto discover. */
        private boolean autoDiscover = true;

        /** The auto discovery task. */
        private long autoDiscoveryTask = 0;

        /** The ip address. */
        private String ipAddress = "";

        /** The domain id. */
        private long domainId = -1;

        /** The name. */
        private String name = "";

        /** The device type. */
        private String deviceType = "";

        /**
         * Checks if is auto discover.
         *
         * @return true, if is auto discover
         */
        @XmlElement
        public boolean isAutoDiscover() {
            return autoDiscover;
        }

        /**
         * Sets the auto discover.
         *
         * @param autoDiscover the new auto discover
         */
        public void setAutoDiscover(boolean autoDiscover) {
            this.autoDiscover = autoDiscover;
        }

        /**
         * Gets the auto discovery task.
         *
         * @return the auto discovery task
         */
        @XmlElement
        public long getAutoDiscoveryTask() {
            return autoDiscoveryTask;
        }

        /**
         * Sets the auto discovery task.
         *
         * @param autoDiscoveryTask the new auto discovery task
         */
        public void setAutoDiscoveryTask(long autoDiscoveryTask) {
            this.autoDiscoveryTask = autoDiscoveryTask;
        }

        /**
         * Gets the ip address.
         *
         * @return the ip address
         */
        @XmlElement
        public String getIpAddress() {
            return ipAddress;
        }

        /**
         * Sets the ip address.
         *
         * @param ipAddress the new ip address
         */
        public void setIpAddress(String ipAddress) {
            this.ipAddress = ipAddress;
        }

        /**
         * Gets the domain id.
         *
         * @return the domain id
         */
        @XmlElement
        public long getDomainId() {
            return domainId;
        }

        /**
         * Sets the domain id.
         *
         * @param domainId the new domain id
         */
        public void setDomainId(long domainId) {
            this.domainId = domainId;
        }

        /**
         * Gets the name.
         *
         * @return the name
         */
        @XmlElement
        public String getName() {
            return name;
        }

        /**
         * Sets the name.
         *
         * @param name the new name
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * Gets the device type.
         *
         * @return the device type
         */
        @XmlElement
        public String getDeviceType() {
            return deviceType;
        }

        /**
         * Sets the device type.
         *
         * @param deviceType the new device type
         */
        public void setDeviceType(String deviceType) {
            this.deviceType = deviceType;
        }
    }

    /**
     * Adds the device.
     *
     * @param request the request
     * @param device the device
     * @return the task
     * @throws WebApplicationException the web application exception
     */
    @SuppressWarnings("unchecked")
    @POST
    @Path("devices")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Task addDevice(@Context HttpServletRequest request, RsNewDevice device) throws WebApplicationException {
        logger.debug("REST request, new device.");
        Network4Address deviceAddress;
        try {
            deviceAddress = new Network4Address(device.getIpAddress());
            if (!deviceAddress.isNormalUnicast()) {
                logger.warn("User posted an invalid IP address (not normal unicast).");
                throw new NetshotBadRequestException("Invalid IP address",
                        NetshotBadRequestException.NETSHOT_INVALID_IP_ADDRESS);
            }
        } catch (UnknownHostException e) {
            logger.warn("User posted an invalid IP address.");
            throw new NetshotBadRequestException("Malformed IP address",
                    NetshotBadRequestException.NETSHOT_MALFORMED_IP_ADDRESS);
        }
        Domain domain;
        List<DeviceCredentialSet> knownCommunities;
        Session session = Database.getSession();
        try {
            logger.debug("Looking for an existing device with this IP address.");
            Device duplicate = (Device) session.createQuery("from Device d where d.mgmtAddress.address = :ip")
                    .setInteger("ip", deviceAddress.getIntAddress()).uniqueResult();
            if (duplicate != null) {
                logger.error("Device {} is already present with this IP address.", duplicate.getId());
                throw new NetshotBadRequestException(
                        String.format("The device '%s' already exists with this IP address.", duplicate.getName()),
                        NetshotBadRequestException.NETSHOT_DUPLICATE_DEVICE);
            }
            domain = (Domain) session.load(Domain.class, device.getDomainId());
            knownCommunities = session
                    .createQuery("from DeviceSnmpCommunity c where c.mgmtDomain is null or c.mgmtDomain = :domain")
                    .setEntity("domain", domain).list();
            if (knownCommunities.size() == 0) {
                logger.error("No available SNMP community");
                throw new NetshotBadRequestException(
                        "There is no known SNMP community in the database to poll the device.",
                        NetshotBadRequestException.NETSHOT_CREDENTIALS_NOTFOUND);
            }
        } catch (ObjectNotFoundException e) {
            logger.error("Non existing domain.", e);
            throw new NetshotBadRequestException("Invalid domain",
                    NetshotBadRequestException.NETSHOT_INVALID_DOMAIN);
        } catch (HibernateException e) {
            logger.error("Error while loading domain or communities.", e);
            throw new NetshotBadRequestException("Database error",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
        User user = (User) request.getSession().getAttribute("user");
        if (device.isAutoDiscover()) {
            try {
                DiscoverDeviceTypeTask task = new DiscoverDeviceTypeTask(deviceAddress, domain,
                        String.format("Device added by %s", user.getUsername()), user.getUsername());
                task.setComments(String.format("Autodiscover device %s", deviceAddress.getIp()));
                for (DeviceCredentialSet credentialSet : knownCommunities) {
                    task.addCredentialSet(credentialSet);
                }
                TaskManager.addTask(task);
                return task;
            } catch (SchedulerException e) {
                logger.error("Unable to schedule the discovery task.", e);
                throw new NetshotBadRequestException("Unable to schedule the task",
                        NetshotBadRequestException.NETSHOT_SCHEDULE_ERROR);
            } catch (HibernateException e) {
                logger.error("Error while adding the discovery task.", e);
                throw new NetshotBadRequestException("Database error",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            }
        } else {
            DeviceDriver driver = DeviceDriver.getDriverByName(device.getDeviceType());
            if (driver == null) {
                logger.warn("Invalid posted device driver.");
                throw new NetshotBadRequestException("Invalid device type.",
                        NetshotBadRequestException.NETSHOT_INVALID_DEVICE_CLASSNAME);
            }
            session = Database.getSession();
            TakeSnapshotTask task;
            Device newDevice = null;
            try {
                session.beginTransaction();
                newDevice = new Device(driver.getName(), deviceAddress, domain, user.getUsername());
                session.save(newDevice);
                task = new TakeSnapshotTask(newDevice, "Initial snapshot after device creation",
                        user.getUsername());
                session.save(task);
                session.getTransaction().commit();
            } catch (Exception e) {
                session.getTransaction().rollback();
                logger.error("Error while creating the device", e);
                throw new NetshotBadRequestException("Database error",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
            if (newDevice != null) {
                DynamicDeviceGroup.refreshAllGroups(newDevice);
            }
            try {
                TaskManager.addTask(task);
                return task;
            } catch (HibernateException e) {
                logger.error("Unable to add the task.", e);
                throw new NetshotBadRequestException("Unable to add the task to the database.",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } catch (SchedulerException e) {
                logger.error("Unable to schedule the task.", e);
                throw new NetshotBadRequestException("Unable to schedule the task.",
                        NetshotBadRequestException.NETSHOT_SCHEDULE_ERROR);
            }
        }

    }

    /**
     * Delete device.
     *
     * @param request the request
     * @param id the id
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("devices/{id}")
    @RolesAllowed("readwrite")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deleteDevice(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete device {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            Device device = (Device) session.load(Device.class, id);
            for (DeviceGroup group : device.getOwnerGroups()) {
                group.deleteCachedDevice(device);
            }
            session.delete(device);
            session.getTransaction().commit();
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to delete the device {}.", id, e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("foreign key constraint fails")) {
                throw new NetshotBadRequestException(
                        "Unable to delete the device, there must be other objects using it.",
                        NetshotBadRequestException.NETSHOT_USED_DEVICE);
            }
            throw new NetshotBadRequestException("Unable to delete the device",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsDevice.
     */
    @XmlRootElement(name = "device")
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsDevice {

        /** The id. */
        private long id = -1;

        /** The enable. */
        private Boolean enabled = null;

        /** The comments. */
        private String comments = null;

        /** The ip address. */
        private String ipAddress = null;

        /** The auto try credentials. */
        private Boolean autoTryCredentials = null;

        /** The credential set ids. */
        private List<Long> credentialSetIds = null;

        private List<Long> clearCredentialSetIds = null;

        private Long mgmtDomain = null;

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Gets the comments.
         *
         * @return the comments
         */
        @XmlElement
        public String getComments() {
            return comments;
        }

        /**
         * Sets the comments.
         *
         * @param comments the new comments
         */
        public void setComments(String comments) {
            this.comments = comments;
        }

        /**
         * Gets the ip address.
         *
         * @return the ip address
         */
        @XmlElement
        public String getIpAddress() {
            return ipAddress;
        }

        /**
         * Sets the ip address.
         *
         * @param ipAddress the new ip address
         */
        public void setIpAddress(String ipAddress) {
            this.ipAddress = ipAddress;
        }

        /**
         * Checks if is auto try credentials.
         *
         * @return true, if is auto try credentials
         */
        @XmlElement
        public Boolean isAutoTryCredentials() {
            return autoTryCredentials;
        }

        /**
         * Sets the auto try credentials.
         *
         * @param autoTryCredentials the new auto try credentials
         */
        public void setAutoTryCredentials(Boolean autoTryCredentials) {
            this.autoTryCredentials = autoTryCredentials;
        }

        /**
         * Gets the credential set ids.
         *
         * @return the credential set ids
         */
        @XmlElement
        public List<Long> getCredentialSetIds() {
            return credentialSetIds;
        }

        /**
         * Sets the credential set ids.
         *
         * @param credentialSetIds the new credential set ids
         */
        public void setCredentialSetIds(List<Long> credentialSetIds) {
            this.credentialSetIds = credentialSetIds;
        }

        /**
         * Instantiates a new rs device.
         */
        public RsDevice() {

        }

        /**
         * Checks if is enable.
         *
         * @return true, if is enable
         */
        @XmlElement
        public Boolean isEnabled() {
            return enabled;
        }

        /**
         * Sets the enable.
         *
         * @param enable the new enable
         */
        public void setEnabled(Boolean enabled) {
            this.enabled = enabled;
        }

        @XmlElement
        public Long getMgmtDomain() {
            return mgmtDomain;
        }

        public void setMgmtDomain(Long mgmtDomain) {
            this.mgmtDomain = mgmtDomain;
        }

        @XmlElement
        public List<Long> getClearCredentialSetIds() {
            return clearCredentialSetIds;
        }

        public void setClearCredentialSetIds(List<Long> clearCredentialSetIds) {
            this.clearCredentialSetIds = clearCredentialSetIds;
        }
    }

    /**
     * Sets the device.
     *
     * @param request the request
     * @param id the id
     * @param rsDevice the rs device
     * @return the device
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("devices/{id}")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Device setDevice(@Context HttpServletRequest request, @PathParam("id") Long id, RsDevice rsDevice)
            throws WebApplicationException {
        logger.debug("REST request, edit device {}.", id);
        Device device;
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            device = (Device) session.load(Device.class, id);
            if (rsDevice.isEnabled() != null) {
                if (rsDevice.isEnabled()) {
                    device.setStatus(Status.INPRODUCTION);
                } else {
                    device.setStatus(Status.DISABLED);
                }
            }
            if (rsDevice.getIpAddress() != null) {
                Network4Address v4Address = new Network4Address(rsDevice.getIpAddress());
                if (!v4Address.isNormalUnicast()) {
                    session.getTransaction().rollback();
                    throw new NetshotBadRequestException("Invalid IP address",
                            NetshotBadRequestException.NETSHOT_INVALID_IP_ADDRESS);
                }
                device.setMgmtAddress(v4Address);
            }
            if (rsDevice.getComments() != null) {
                device.setComments(rsDevice.getComments());
            }
            if (rsDevice.getCredentialSetIds() != null) {
                if (rsDevice.getClearCredentialSetIds() == null) {
                    device.clearCredentialSets();
                } else {
                    Iterator<DeviceCredentialSet> csIterator = device.getCredentialSets().iterator();
                    while (csIterator.hasNext()) {
                        if (rsDevice.getClearCredentialSetIds().contains(csIterator.next().getId())) {
                            csIterator.remove();
                        }
                    }
                }
                for (Long credentialSetId : rsDevice.getCredentialSetIds()) {
                    try {
                        DeviceCredentialSet credentialSet = (DeviceCredentialSet) session
                                .load(DeviceCredentialSet.class, credentialSetId);
                        device.addCredentialSet(credentialSet);
                    } catch (ObjectNotFoundException e) {
                        logger.error("Non existing credential set {}.", credentialSetId);
                    }
                }
            }
            if (rsDevice.isAutoTryCredentials() != null) {
                device.setAutoTryCredentials(rsDevice.isAutoTryCredentials());
            }
            if (rsDevice.getMgmtDomain() != null) {
                Domain domain = (Domain) session.load(Domain.class, rsDevice.getMgmtDomain());
                device.setMgmtDomain(domain);
            }
            session.update(device);
            session.getTransaction().commit();
        } catch (UnknownHostException e) {
            session.getTransaction().rollback();
            logger.warn("User posted an invalid IP address.", e);
            throw new NetshotBadRequestException("Malformed IP address",
                    NetshotBadRequestException.NETSHOT_MALFORMED_IP_ADDRESS);
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The device doesn't exist.", e);
            throw new NetshotBadRequestException("The device doesn't exist anymore.",
                    NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Cannot edit the device.", e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A device with this IP address already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_DEVICE);
            }
            if (t != null && t.getMessage().contains("domain")) {
                throw new NetshotBadRequestException("Unable to find the domain",
                        NetshotBadRequestException.NETSHOT_INVALID_DOMAIN);
            }
            throw new NetshotBadRequestException("Unable to save the device.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
        DynamicDeviceGroup.refreshAllGroups(device);
        return this.getDevice(id);
    }

    /**
     * Gets the task.
     *
     * @param request the request
     * @param id the id
     * @return the task
     */
    @GET
    @Path("tasks/{id}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Task getTask(@PathParam("id") Long id) {
        logger.debug("REST request, get task {}", id);
        Session session = Database.getSession();
        Task task;
        try {
            task = (Task) session.get(Task.class, id);
            return task;
        } catch (ObjectNotFoundException e) {
            logger.error("Unable to find the task {}.", id, e);
            throw new NetshotBadRequestException("Task not found", NetshotBadRequestException.NETSHOT_INVALID_TASK);
        } catch (HibernateException e) {
            logger.error("Unable to fetch the task {}.", id, e);
            throw new NetshotBadRequestException("Unable to fetch the task",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the tasks.
     *
     * @param request the request
     * @return the tasks
     */
    @GET
    @Path("tasks")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<Task> getTasks() {
        logger.debug("REST request, get tasks.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<Task> tasks = session.createQuery("from Task t order by t.id desc").list();
            return tasks;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the tasks.", e);
            throw new NetshotBadRequestException("Unable to fetch the tasks",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the credential sets.
     *
     * @param request the request
     * @return the credential sets
     * @throws WebApplicationException the web application exception
     */
    @SuppressWarnings("unchecked")
    @GET
    @Path("credentialsets")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<DeviceCredentialSet> getCredentialSets() throws WebApplicationException {
        logger.debug("REST request, get credentials.");
        Session session = Database.getSession();
        List<DeviceCredentialSet> credentialSets;
        try {
            credentialSets = session.createCriteria(DeviceCredentialSet.class).list();
        } catch (HibernateException e) {
            logger.error("Unable to fetch the credentials.", e);
            throw new NetshotBadRequestException("Unable to fetch the credentials",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
        for (DeviceCredentialSet credentialSet : credentialSets) {
            if (DeviceCliAccount.class.isInstance(credentialSet)) {
                ((DeviceCliAccount) credentialSet).setPassword("=");
                ((DeviceCliAccount) credentialSet).setSuperPassword("=");
            }
        }
        return credentialSets;
    }

    /**
     * Delete credential set.
     *
     * @param request the request
     * @param id the id
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("credentialsets/{id}")
    @RolesAllowed("admin")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deleteCredentialSet(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete credentials {}", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            DeviceCredentialSet credentialSet = (DeviceCredentialSet) session.load(DeviceCredentialSet.class, id);
            session.delete(credentialSet);
            session.getTransaction().commit();
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to delete the credentials {}", id, e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("foreign key constraint fails")) {
                throw new NetshotBadRequestException(
                        "Unable to delete the credential set, there must be devices or tasks using it.",
                        NetshotBadRequestException.NETSHOT_USED_CREDENTIALS);
            }
            throw new NetshotBadRequestException("Unable to delete the credential set",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Adds the credential set.
     *
     * @param request the request
     * @param credentialSet the credential set
     * @return the device credential set
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("credentialsets")
    @RolesAllowed("admin")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void addCredentialSet(DeviceCredentialSet credentialSet) throws WebApplicationException {
        logger.debug("REST request, add credentials.");
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            if (credentialSet.getMgmtDomain() != null) {
                credentialSet
                        .setMgmtDomain((Domain) session.load(Domain.class, credentialSet.getMgmtDomain().getId()));
            }
            session.save(credentialSet);
            session.getTransaction().commit();
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            Throwable t = e.getCause();
            logger.error("Can't add the credentials.", e);
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A credential set with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_CREDENTIALS);
            } else if (t != null && t.getMessage().contains("mgmt_domain")) {
                throw new NetshotBadRequestException("The domain doesn't exist.",
                        NetshotBadRequestException.NETSHOT_INVALID_DOMAIN);
            }
            throw new NetshotBadRequestException("Unable to save the credential set",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Sets the credential set.
     *
     * @param request the request
     * @param id the id
     * @param rsCredentialSet the rs credential set
     * @return the device credential set
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("credentialsets/{id}")
    @RolesAllowed("admin")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public DeviceCredentialSet setCredentialSet(@PathParam("id") Long id, DeviceCredentialSet rsCredentialSet)
            throws WebApplicationException {
        logger.debug("REST request, edit credentials {}", id);
        Session session = Database.getSession();
        DeviceCredentialSet credentialSet;
        try {
            session.beginTransaction();
            credentialSet = (DeviceCredentialSet) session.get(rsCredentialSet.getClass(), id);
            if (credentialSet == null) {
                logger.error("Unable to find the credential set {}.", id);
                throw new NetshotBadRequestException("Unable to find the credential set.",
                        NetshotBadRequestException.NETSHOT_CREDENTIALS_NOTFOUND);
            }
            if (!credentialSet.getClass().equals(rsCredentialSet.getClass())) {
                logger.error("Wrong posted credential type for credential set {}.", id);
                throw new NetshotBadRequestException("The posted credential type doesn't match the existing one.",
                        NetshotBadRequestException.NETSHOT_INVALID_CREDENTIALS_TYPE);
            }
            if (rsCredentialSet.getMgmtDomain() == null) {
                credentialSet.setMgmtDomain(null);
            } else {
                credentialSet.setMgmtDomain(
                        (Domain) session.load(Domain.class, rsCredentialSet.getMgmtDomain().getId()));
            }
            credentialSet.setName(rsCredentialSet.getName());
            if (DeviceCliAccount.class.isInstance(credentialSet)) {
                DeviceCliAccount cliAccount = (DeviceCliAccount) credentialSet;
                DeviceCliAccount rsCliAccount = (DeviceCliAccount) rsCredentialSet;
                cliAccount.setUsername(rsCliAccount.getUsername());
                if (!rsCliAccount.getPassword().equals("=")) {
                    cliAccount.setPassword(rsCliAccount.getPassword());
                }
                if (!rsCliAccount.getSuperPassword().equals("=")) {
                    cliAccount.setSuperPassword(rsCliAccount.getSuperPassword());
                }
                if (DeviceSshKeyAccount.class.isInstance(credentialSet)) {
                    ((DeviceSshKeyAccount) cliAccount)
                            .setPublicKey(((DeviceSshKeyAccount) rsCliAccount).getPublicKey());
                    ((DeviceSshKeyAccount) cliAccount)
                            .setPrivateKey(((DeviceSshKeyAccount) rsCliAccount).getPrivateKey());
                }
            } else if (DeviceSnmpCommunity.class.isInstance(credentialSet)) {
                ((DeviceSnmpCommunity) credentialSet)
                        .setCommunity(((DeviceSnmpCommunity) rsCredentialSet).getCommunity());
            }
            session.update(credentialSet);
            session.getTransaction().commit();
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            Throwable t = e.getCause();
            logger.error("Unable to save the credentials {}.", id, e);
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A credential set with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_CREDENTIALS);
            } else if (t != null && t.getMessage().contains("mgmt_domain")) {
                throw new NetshotBadRequestException("The domain doesn't exist.",
                        NetshotBadRequestException.NETSHOT_INVALID_DOMAIN);
            }
            throw new NetshotBadRequestException("Unable to save the credential set",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } catch (NetshotBadRequestException e) {
            session.getTransaction().rollback();
            throw e;
        } finally {
            session.close();
        }
        return credentialSet;
    }

    /**
     * The Class RsSearchCriteria.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsSearchCriteria {

        /** The device class name. */
        private String driver;

        /** The query. */
        private String query;

        /**
         * Gets the device class name.
         *
         * @return the device class name
         */
        @XmlElement
        public String getDriver() {
            return driver;
        }

        public void setDriver(String driver) {
            this.driver = driver;
        }

        /**
         * Gets the query.
         *
         * @return the query
         */
        @XmlElement
        public String getQuery() {
            return query;
        }

        /**
         * Sets the query.
         *
         * @param query the new query
         */
        public void setQuery(String query) {
            this.query = query;
        }
    }

    /**
     * The Class RsSearchResults.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsSearchResults {

        /** The query. */
        private String query;

        /** The devices. */
        private List<RsLightDevice> devices;

        /**
         * Gets the query.
         *
         * @return the query
         */
        @XmlElement
        public String getQuery() {
            return query;
        }

        /**
         * Sets the query.
         *
         * @param query the new query
         */
        public void setQuery(String query) {
            this.query = query;
        }

        /**
         * Gets the devices.
         *
         * @return the devices
         */
        @XmlElement
        public List<RsLightDevice> getDevices() {
            return devices;
        }

        /**
         * Sets the devices.
         *
         * @param devices the new devices
         */
        public void setDevices(List<RsLightDevice> devices) {
            this.devices = devices;
        }
    }

    /**
     * Search devices.
     *
     * @param request the request
     * @param criteria the criteria
     * @return the rs search results
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("devices/search")
    @RolesAllowed("readonly")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public RsSearchResults searchDevices(RsSearchCriteria criteria) throws WebApplicationException {
        logger.debug("REST request, search devices, query '{}', driver '{}'.", criteria.getQuery(),
                criteria.getDriver());

        DeviceDriver driver = DeviceDriver.getDriverByName(criteria.getDriver());
        try {
            Finder finder = new Finder(criteria.getQuery(), driver);
            Session session = Database.getSession();
            try {
                Query query = session.createQuery(DEVICELIST_BASEQUERY + finder.getHql());
                finder.setVariables(query);
                @SuppressWarnings("unchecked")
                List<RsLightDevice> devices = query
                        .setResultTransformer(Transformers.aliasToBean(RsLightDevice.class)).list();
                RsSearchResults results = new RsSearchResults();
                results.setDevices(devices);
                results.setQuery(finder.getFormattedQuery());
                return results;
            } catch (HibernateException e) {
                logger.error("Error while searching for the devices.", e);
                throw new NetshotBadRequestException("Unable to fetch the devices",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
        } catch (FinderParseException e) {
            logger.warn("User's query is invalid.", e);
            throw new NetshotBadRequestException("Invalid search string. " + e.getMessage(),
                    NetshotBadRequestException.NETSHOT_INVALID_SEARCH_STRING);
        }
    }

    /**
     * Adds the group.
     *
     * @param request the request
     * @param deviceGroup the device group
     * @return the device group
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("groups")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public DeviceGroup addGroup(DeviceGroup deviceGroup) throws WebApplicationException {
        logger.debug("REST request, add group.");
        String name = deviceGroup.getName().trim();
        if (name.isEmpty()) {
            logger.warn("User posted an empty group name.");
            throw new NetshotBadRequestException("Invalid group name.",
                    NetshotBadRequestException.NETSHOT_INVALID_GROUP_NAME);
        }
        deviceGroup.setName(name);
        deviceGroup.setId(0);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            session.save(deviceGroup);
            session.getTransaction().commit();
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the new device group.", e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A group with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_GROUP);
            }
            throw new NetshotBadRequestException("Unable to add the group to the database",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
        return deviceGroup;
    }

    /**
     * Gets the groups.
     *
     * @param request the request
     * @return the groups
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("groups")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<DeviceGroup> getGroups() throws WebApplicationException {
        logger.debug("REST request, get groups.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<DeviceGroup> deviceGroups = session.createCriteria(DeviceGroup.class).list();
            return deviceGroups;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the groups.", e);
            throw new NetshotBadRequestException("Unable to fetch the groups",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Delete group.
     *
     * @param request the request
     * @param id the id
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("groups/{id}")
    @RolesAllowed("readwrite")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deleteGroup(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete group {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            DeviceGroup deviceGroup = (DeviceGroup) session.load(DeviceGroup.class, id);
            for (Policy policy : deviceGroup.getAppliedPolicies()) {
                policy.setTargetGroup(null);
                session.save(policy);
            }
            session.delete(deviceGroup);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The group {} to be deleted doesn't exist.", id, e);
            throw new NetshotBadRequestException("The group doesn't exist.",
                    NetshotBadRequestException.NETSHOT_INVALID_GROUP);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to delete the group {}.", id, e);
            throw new NetshotBadRequestException("Unable to delete the group",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsDeviceGroup.
     */
    @XmlRootElement(name = "group")
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsDeviceGroup {

        /** The id. */
        private long id = -1;

        /** The type. */
        private String type;

        /** The static devices. */
        private List<Long> staticDevices = new ArrayList<Long>();

        /** The device class name. */
        private String driver;

        /** The query. */
        private String query;

        /** The folder. */
        private String folder = "";

        /** Hide the group in reports. */
        private boolean hiddenFromReports = false;

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Instantiates a new rs device group.
         */
        public RsDeviceGroup() {

        }

        /**
         * Gets the type.
         *
         * @return the type
         */
        @XmlElement
        public String getType() {
            return type;
        }

        /**
         * Sets the type.
         *
         * @param type the new type
         */
        public void setType(String type) {
            this.type = type;
        }

        /**
         * Gets the static devices.
         *
         * @return the static devices
         */
        @XmlElement
        public List<Long> getStaticDevices() {
            return staticDevices;
        }

        /**
         * Sets the static devices.
         *
         * @param staticDevices the new static devices
         */
        public void setStaticDevices(List<Long> staticDevices) {
            this.staticDevices = staticDevices;
        }

        /**
         * Gets the device class name.
         *
         * @return the device class name
         */
        @XmlElement
        public String getDriver() {
            return driver;
        }

        public void setDriver(String driver) {
            this.driver = driver;
        }

        /**
         * Gets the query.
         *
         * @return the query
         */
        @XmlElement
        public String getQuery() {
            return query;
        }

        /**
         * Sets the query.
         *
         * @param query the new query
         */
        public void setQuery(String query) {
            this.query = query;
        }

        @XmlElement
        public String getFolder() {
            return folder;
        }

        public void setFolder(String folder) {
            this.folder = folder;
        }

        @XmlElement
        public boolean isHiddenFromReports() {
            return hiddenFromReports;
        }

        public void setHiddenFromReports(boolean hiddenFromReports) {
            this.hiddenFromReports = hiddenFromReports;
        }

    }

    /**
     * Sets the group.
     *
     * @param request the request
     * @param id the id
     * @param rsGroup the rs group
     * @return the device group
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("groups/{id}")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public DeviceGroup setGroup(@PathParam("id") Long id, RsDeviceGroup rsGroup) throws WebApplicationException {
        logger.debug("REST request, edit group {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            DeviceGroup group = (DeviceGroup) session.get(DeviceGroup.class, id);
            if (group == null) {
                logger.error("Unable to find the group {} to be edited.", id);
                throw new NetshotBadRequestException("Unable to find this group.",
                        NetshotBadRequestException.NETSHOT_INVALID_GROUP);
            }
            if (group instanceof StaticDeviceGroup) {
                StaticDeviceGroup staticGroup = (StaticDeviceGroup) group;
                Set<Device> devices = new HashSet<Device>();
                for (Long deviceId : rsGroup.getStaticDevices()) {
                    Device device = (Device) session.load(Device.class, deviceId);
                    devices.add(device);
                }
                staticGroup.updateCachedDevices(devices);
            } else if (group instanceof DynamicDeviceGroup) {
                DynamicDeviceGroup dynamicGroup = (DynamicDeviceGroup) group;
                dynamicGroup.setDriver(rsGroup.getDriver());
                dynamicGroup.setQuery(rsGroup.getQuery());
                try {
                    dynamicGroup.refreshCache(session);
                } catch (FinderParseException e) {
                    throw new NetshotBadRequestException("Invalid query for the group definition.",
                            NetshotBadRequestException.NETSHOT_INVALID_DYNAMICGROUP_QUERY);
                }
            } else {
                throw new NetshotBadRequestException("Unknown group type.",
                        NetshotBadRequestException.NETSHOT_INCOMPATIBLE_GROUP_TYPE);
            }
            group.setFolder(rsGroup.getFolder());
            group.setHiddenFromReports(rsGroup.isHiddenFromReports());
            session.update(group);
            session.getTransaction().commit();
            return group;
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("Unable to find a device while editing group {}.", id, e);
            throw new NetshotBadRequestException("Unable to find a device. Refresh and try again.",
                    NetshotBadRequestException.NETSHOT_INVALID_DEVICE_IN_STATICGROUP);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to save the group {}.", id, e);
            throw new NetshotBadRequestException("Unable to save the group.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } catch (WebApplicationException e) {
            session.getTransaction().rollback();
            throw e;
        } finally {
            session.close();
        }
    }

    /**
     * Gets the group devices.
     *
     * @param request the request
     * @param id the id
     * @return the group devices
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("devices/group/{id}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsLightDevice> getGroupDevices(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, get devices from group {}.", id);
        Session session = Database.getSession();
        DeviceGroup group;
        try {
            group = (DeviceGroup) session.get(DeviceGroup.class, id);
            if (group == null) {
                logger.error("Unable to find the group {}.", id);
                throw new NetshotBadRequestException("Can't find this group",
                        NetshotBadRequestException.NETSHOT_INVALID_GROUP);
            }
            Query query = session.createQuery(
                    RestService.DEVICELIST_BASEQUERY + "from Device d join d.ownerGroups g where g.id = :id")
                    .setLong("id", id);
            @SuppressWarnings("unchecked")
            List<RsLightDevice> devices = query.setResultTransformer(Transformers.aliasToBean(RsLightDevice.class))
                    .list();
            return devices;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the devices of group {}.", id, e);
            throw new NetshotBadRequestException("Unable to fetch the devices",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsTask.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsTask {

        /** The id. */
        private long id;

        /** The cancelled. */
        private boolean cancelled = false;

        /** The type. */
        private String type = "";

        /** The group. */
        private Long group = new Long(0);

        /** The device. */
        private Long device = new Long(0);

        /** The domain. */
        private Long domain = new Long(0);

        /** The subnets. */
        private String subnets = "";

        /** The IP addresses. */
        private String ipAddresses = "";

        /** The schedule reference. */
        private Date scheduleReference = new Date();

        /** The schedule type. */
        private Task.ScheduleType scheduleType = ScheduleType.ASAP;

        /** The comments. */
        private String comments = "";

        private int limitToOutofdateDeviceHours = -1;

        private int daysToPurge = 90;

        private int configDaysToPurge = -1;

        private int configSizeToPurge = 0;

        private int configKeepDays = 0;

        private String script = "";

        private String driver;

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Checks if is cancelled.
         *
         * @return true, if is cancelled
         */
        @XmlElement
        public boolean isCancelled() {
            return cancelled;
        }

        /**
         * Sets the cancelled.
         *
         * @param cancelled the new cancelled
         */
        public void setCancelled(boolean cancelled) {
            this.cancelled = cancelled;
        }

        /**
         * Gets the type.
         *
         * @return the type
         */
        @XmlElement
        public String getType() {
            return type;
        }

        /**
         * Sets the type.
         *
         * @param type the new type
         */
        public void setType(String type) {
            this.type = type;
        }

        /**
         * Gets the group.
         *
         * @return the group
         */
        @XmlElement
        public Long getGroup() {
            return group;
        }

        /**
         * Sets the group.
         *
         * @param group the new group
         */
        public void setGroup(Long group) {
            this.group = group;
        }

        /**
         * Gets the device.
         *
         * @return the device
         */
        @XmlElement
        public Long getDevice() {
            return device;
        }

        /**
         * Sets the device.
         *
         * @param device the new device
         */
        public void setDevice(Long device) {
            this.device = device;
        }

        /**
         * Gets the subnets.
         *
         * @return the subnets
         */
        @XmlElement
        public String getSubnets() {
            return subnets;
        }

        /**
         * Sets the subnets.
         *
         * @param subnets the new subnets
         */
        public void setSubnets(String subnet) {
            this.subnets = subnet;
        }

        /**
         * Gets the schedule reference.
         *
         * @return the schedule reference
         */
        @XmlElement
        public Date getScheduleReference() {
            return scheduleReference;
        }

        /**
         * Sets the schedule reference.
         *
         * @param scheduleReference the new schedule reference
         */
        public void setScheduleReference(Date scheduleReference) {
            this.scheduleReference = scheduleReference;
        }

        /**
         * Gets the schedule type.
         *
         * @return the schedule type
         */
        @XmlElement
        public Task.ScheduleType getScheduleType() {
            return scheduleType;
        }

        /**
         * Sets the schedule type.
         *
         * @param scheduleType the new schedule type
         */
        public void setScheduleType(Task.ScheduleType scheduleType) {
            this.scheduleType = scheduleType;
        }

        /**
         * Gets the comments.
         *
         * @return the comments
         */
        @XmlElement
        public String getComments() {
            return comments;
        }

        /**
         * Sets the comments.
         *
         * @param comments the new comments
         */
        public void setComments(String comments) {
            this.comments = comments;
        }

        /**
         * Gets the domain.
         *
         * @return the domain
         */
        @XmlElement
        public Long getDomain() {
            return domain;
        }

        /**
         * Sets the domain.
         *
         * @param domain the new domain
         */
        public void setDomain(Long domain) {
            this.domain = domain;
        }

        /**
         * Gets the ip addresses.
         *
         * @return the ip addresses
         */
        public String getIpAddresses() {
            return ipAddresses;
        }

        /**
         * Sets the ip addresses.
         *
         * @param ipAddresses the new ip addresses
         */
        public void setIpAddresses(String ipAddresses) {
            this.ipAddresses = ipAddresses;
        }

        /**
         * Gets the limit to outofdate device hours.
         *
         * @return the limit to outofdate device hours
         */
        @XmlElement
        public int getLimitToOutofdateDeviceHours() {
            return limitToOutofdateDeviceHours;
        }

        public void setLimitToOutofdateDeviceHours(int limitToOutofdateDeviceHours) {
            this.limitToOutofdateDeviceHours = limitToOutofdateDeviceHours;
        }

        @XmlElement
        public int getDaysToPurge() {
            return daysToPurge;
        }

        public void setDaysToPurge(int days) {
            this.daysToPurge = days;
        }

        @XmlElement
        public String getScript() {
            return script;
        }

        public void setScript(String script) {
            this.script = script;
        }

        @XmlElement
        public String getDriver() {
            return driver;
        }

        public void setDriver(String driver) {
            this.driver = driver;
        }

        @XmlElement
        public int getConfigDaysToPurge() {
            return configDaysToPurge;
        }

        public void setConfigDaysToPurge(int configDaysToPurge) {
            this.configDaysToPurge = configDaysToPurge;
        }

        @XmlElement
        public int getConfigSizeToPurge() {
            return configSizeToPurge;
        }

        public void setConfigSizeToPurge(int configSizeToPurge) {
            this.configSizeToPurge = configSizeToPurge;
        }

        @XmlElement
        public int getConfigKeepDays() {
            return configKeepDays;
        }

        public void setConfigKeepDays(int configKeepDays) {
            this.configKeepDays = configKeepDays;
        }

    }

    /**
     * Sets the task.
     *
     * @param request the request
     * @param id the id
     * @param rsTask the rs task
     * @return the task
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("tasks/{id}")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Task setTask(@PathParam("id") Long id, RsTask rsTask) throws WebApplicationException {
        logger.debug("REST request, edit task {}.", id);
        Task task = null;
        Session session = Database.getSession();
        try {
            task = (Task) session.get(Task.class, id);
        } catch (HibernateException e) {
            logger.error("Unable to fetch the task {}.", id, e);
            throw new NetshotBadRequestException("Unable to fetch the task.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }

        if (task == null) {
            logger.error("Unable to find the task {}.", id);
            throw new NetshotBadRequestException("Unable to find the task.",
                    NetshotBadRequestException.NETSHOT_INVALID_TASK);
        }

        if (rsTask.isCancelled()) {
            if (task.getStatus() != Task.Status.SCHEDULED) {
                logger.error("User is trying to cancel task {} not in SCHEDULE state.", id);
                throw new NetshotBadRequestException("The task isn't in 'SCHEDULED' state.",
                        NetshotBadRequestException.NETSHOT_TASK_NOT_CANCELLABLE);
            }

            try {
                TaskManager.cancelTask(task, "Task manually cancelled by user."); //TODO
            } catch (Exception e) {
                logger.error("Unable to cancel the task {}.", id, e);
                throw new NetshotBadRequestException("Cannot cancel the task.",
                        NetshotBadRequestException.NETSHOT_TASK_CANCEL_ERROR);
            }
        }

        return task;
    }

    /**
     * The Class RsTaskCriteria.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsTaskCriteria {

        /** The status. */
        private String status = "";

        /** The day. */
        private Date day = new Date();

        /**
         * Gets the status.
         *
         * @return the status
         */
        @XmlElement
        public String getStatus() {
            return status;
        }

        /**
         * Sets the status.
         *
         * @param status the new status
         */
        public void setStatus(String status) {
            this.status = status;
        }

        /**
         * Gets the day.
         *
         * @return the day
         */
        @XmlElement
        public Date getDay() {
            return day;
        }

        /**
         * Sets the day.
         *
         * @param day the new day
         */
        public void setDay(Date day) {
            this.day = day;
        }
    }

    /**
     * Search tasks.
     *
     * @param request the request
     * @param criteria the criteria
     * @return the list
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("tasks/search")
    @RolesAllowed("readonly")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<Task> searchTasks(RsTaskCriteria criteria) throws WebApplicationException {

        logger.debug("REST request, search for tasks.");

        Session session = Database.getSession();
        try {
            Criteria c = session.createCriteria(Task.class);
            Task.Status status = null;
            try {
                if (!"ANY".equals(criteria.getStatus())) {
                    status = Task.Status.valueOf(criteria.getStatus());
                    c.add(Property.forName("status").eq(status));
                }
            } catch (Exception e) {
                logger.warn("Invalid status {}.", criteria.getStatus());
            }
            Calendar min = Calendar.getInstance();
            min.setTime(criteria.getDay());
            min.set(Calendar.HOUR_OF_DAY, 0);
            min.set(Calendar.MINUTE, 0);
            min.set(Calendar.SECOND, 0);
            min.set(Calendar.MILLISECOND, 0);
            Calendar max = (Calendar) min.clone();
            max.add(Calendar.DAY_OF_MONTH, 1);

            if (status == Task.Status.SUCCESS || status == Task.Status.FAILURE) {
                c.add(Property.forName("executionDate").between(min.getTime(), max.getTime()));
            } else if (status == Task.Status.CANCELLED) {
                c.add(Property.forName("changeDate").between(min.getTime(), max.getTime()));
            } else if (status == null) {
                c.add(Restrictions.or(Property.forName("status").eq(Task.Status.RUNNING),
                        Property.forName("status").eq(Task.Status.SCHEDULED),
                        Property.forName("executionDate").between(min.getTime(), max.getTime()),
                        Restrictions.and(Property.forName("executionDate").isNull(),
                                Property.forName("changeDate").between(min.getTime(), max.getTime()))));
            }
            c.addOrder(Property.forName("id").desc());

            @SuppressWarnings("unchecked")
            List<Task> tasks = c.list();
            return tasks;
        } catch (HibernateException e) {
            logger.error("Error while searching for tasks.", e);
            throw new NetshotBadRequestException("Unable to fetch the tasks",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Adds the task.
     *
     * @param request the request
     * @param rsTask the rs task
     * @return the task
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("tasks")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Task addTask(@Context HttpServletRequest request, @Context SecurityContext securityContext,
            RsTask rsTask) throws WebApplicationException {
        logger.debug("REST request, add task.");
        User user = (User) request.getSession().getAttribute("user");
        String userName = "";
        try {
            userName = user.getUsername();
        } catch (Exception e) {
        }

        Task task;
        if (rsTask.getType().equals("TakeSnapshotTask")) {
            logger.trace("Adding a TakeSnapshotTask");
            Device device;
            Session session = Database.getSession();
            try {
                device = (Device) session.get(Device.class, rsTask.getDevice());
                if (device == null) {
                    logger.error("Unable to find the device {}.", rsTask.getDevice());
                    throw new NetshotBadRequestException("Unable to find the device.",
                            NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
                }
            } catch (HibernateException e) {
                logger.error("Error while retrieving the device.", e);
                throw new NetshotBadRequestException("Database error.",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
            task = new TakeSnapshotTask(device, rsTask.getComments(), userName);
        } else if (rsTask.getType().equals("RunDeviceScriptTask")) {
            if (!securityContext.isUserInRole("admin")) {
                throw new NetshotNotAuthorizedException("Must be admin to run scripts on devices.", 0);
            }
            logger.trace("Adding a RunDeviceScriptTask");
            DeviceDriver driver = DeviceDriver.getDriverByName(rsTask.getDriver());
            if (driver == null) {
                logger.error("Unknown device driver {}.", rsTask.getType());
                throw new NetshotBadRequestException("Unknown device driver.",
                        NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
            }
            if (rsTask.getScript() == null) {
                logger.error("The script can't be empty.");
                throw new NetshotBadRequestException("The script can't be empty.",
                        NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
            }
            Device device;
            Session session = Database.getSession();
            try {
                device = (Device) session.get(Device.class, rsTask.getDevice());
                if (device == null) {
                    logger.error("Unable to find the device {}.", rsTask.getDevice());
                    throw new NetshotBadRequestException("Unable to find the device.",
                            NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
                }
            } catch (HibernateException e) {
                logger.error("Error while retrieving the device.", e);
                throw new NetshotBadRequestException("Database error.",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
            task = new RunDeviceScriptTask(device, rsTask.getScript(), driver, rsTask.getComments(), userName);
        } else if (rsTask.getType().equals("RunDeviceGroupScriptTask")) {
            logger.trace("Adding a RunDeviceGroupScriptTask");
            DeviceDriver driver = DeviceDriver.getDriverByName(rsTask.getDriver());
            if (driver == null) {
                logger.error("Unknown device driver {}.", rsTask.getType());
                throw new NetshotBadRequestException("Unknown device driver.",
                        NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
            }
            if (rsTask.getScript() == null) {
                logger.error("The script can't be empty.");
                throw new NetshotBadRequestException("The script can't be empty.",
                        NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
            }
            DeviceGroup group;
            Session session = Database.getSession();
            try {
                group = (DeviceGroup) session.get(DeviceGroup.class, rsTask.getGroup());
                if (group == null) {
                    logger.error("Unable to find the group {}.", rsTask.getGroup());
                    throw new NetshotBadRequestException("Unable to find the group.",
                            NetshotBadRequestException.NETSHOT_INVALID_GROUP);
                }
                task = new RunDeviceGroupScriptTask(group, rsTask.getScript(), driver, rsTask.getComments(),
                        userName);
            } catch (HibernateException e) {
                logger.error("Error while retrieving the group.", e);
                throw new NetshotBadRequestException("Database error.",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
        } else if (rsTask.getType().equals("CheckComplianceTask")) {
            logger.trace("Adding a CheckComplianceTask");
            Device device;
            Session session = Database.getSession();
            try {
                device = (Device) session.get(Device.class, rsTask.getDevice());
                if (device == null) {
                    logger.error("Unable to find the device {}.", rsTask.getDevice());
                    throw new NetshotBadRequestException("Unable to find the device.",
                            NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
                }
            } catch (HibernateException e) {
                logger.error("Error while retrieving the device.", e);
                throw new NetshotBadRequestException("Database error.",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
            task = new CheckComplianceTask(device, rsTask.getComments(), userName);
        } else if (rsTask.getType().equals("TakeGroupSnapshotTask")) {
            logger.trace("Adding a TakeGroupSnapshotTask");
            DeviceGroup group;
            Session session = Database.getSession();
            try {
                group = (DeviceGroup) session.get(DeviceGroup.class, rsTask.getGroup());
                if (group == null) {
                    logger.error("Unable to find the group {}.", rsTask.getGroup());
                    throw new NetshotBadRequestException("Unable to find the group.",
                            NetshotBadRequestException.NETSHOT_INVALID_GROUP);
                }
                task = new TakeGroupSnapshotTask(group, rsTask.getComments(), userName,
                        rsTask.getLimitToOutofdateDeviceHours());
            } catch (HibernateException e) {
                logger.error("Error while retrieving the group.", e);
                throw new NetshotBadRequestException("Database error.",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
        } else if (rsTask.getType().equals("CheckGroupComplianceTask")) {
            logger.trace("Adding a CheckGroupComplianceTask");
            DeviceGroup group;
            Session session = Database.getSession();
            try {
                group = (DeviceGroup) session.get(DeviceGroup.class, rsTask.getGroup());
                if (group == null) {
                    logger.error("Unable to find the group {}.", rsTask.getGroup());
                    throw new NetshotBadRequestException("Unable to find the group.",
                            NetshotBadRequestException.NETSHOT_INVALID_GROUP);
                }
                task = new CheckGroupComplianceTask(group, rsTask.getComments(), userName);
            } catch (HibernateException e) {
                logger.error("Error while retrieving the group.", e);
                throw new NetshotBadRequestException("Database error.",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
        } else if (rsTask.getType().equals("CheckGroupSoftwareTask")) {
            logger.trace("Adding a CheckGroupSoftwareTask");
            DeviceGroup group;
            Session session = Database.getSession();
            try {
                group = (DeviceGroup) session.get(DeviceGroup.class, rsTask.getGroup());
                if (group == null) {
                    logger.error("Unable to find the group {}.", rsTask.getGroup());
                    throw new NetshotBadRequestException("Unable to find the group.",
                            NetshotBadRequestException.NETSHOT_INVALID_GROUP);
                }
                task = new CheckGroupSoftwareTask(group, rsTask.getComments(), userName);
            } catch (HibernateException e) {
                logger.error("Error while retrieving the group.", e);
                throw new NetshotBadRequestException("Database error.",
                        NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
            } finally {
                session.close();
            }
        } else if (rsTask.getType().equals("ScanSubnetsTask")) {
            logger.trace("Adding a ScanSubnetsTask");
            Set<Network4Address> subnets = new HashSet<Network4Address>();
            String[] rsSubnets = rsTask.getSubnets().split("(\r\n|\n|;| |,)");
            Pattern pattern = Pattern.compile("^(?<ip>[0-9\\.]+)(/(?<mask>[0-9]+))?$");
            for (String rsSubnet : rsSubnets) {
                Matcher matcher = pattern.matcher(rsSubnet);
                if (!matcher.find()) {
                    logger.warn("User posted an invalid subnet '{}'.", rsSubnet);
                    throw new NetshotBadRequestException(String.format("Invalid subnet '%s'.", rsSubnet),
                            NetshotBadRequestException.NETSHOT_INVALID_SUBNET);
                }
                Network4Address subnet;
                try {
                    int mask = 32;
                    if (matcher.group("mask") != null) {
                        mask = Integer.parseInt(matcher.group("mask"));
                    }
                    subnet = new Network4Address(matcher.group("ip"), mask);
                    subnets.add(subnet);
                } catch (Exception e) {
                    logger.warn("User posted an invalid subnet '{}'.", rsSubnet, e);
                    throw new NetshotBadRequestException(String.format("Invalid subnet '%s'.", rsSubnet),
                            NetshotBadRequestException.NETSHOT_INVALID_SUBNET);
                }
                if (subnet.getPrefixLength() < 22 || subnet.getPrefixLength() > 32) {
                    logger.warn("User posted an invalid prefix length {}.", subnet.getPrefix());
                    throw new NetshotBadRequestException(String.format("Invalid prefix length for '%s'.", rsSubnet),
                            NetshotBadRequestException.NETSHOT_SCAN_SUBNET_TOO_BIG);
                }
            }
            if (subnets.size() == 0) {
                logger.warn("User posted an invalid subnet list '{}'.", rsTask.getSubnets());
                throw new NetshotBadRequestException(
                        String.format("Invalid subnet list '%s'.", rsTask.getSubnets()),
                        NetshotBadRequestException.NETSHOT_INVALID_SUBNET);
            }
            Domain domain;
            if (rsTask.getDomain() == 0) {
                logger.error("Domain {} is invalid (0).", rsTask.getDomain());
                throw new NetshotBadRequestException("Invalid domain",
                        NetshotBadRequestException.NETSHOT_INVALID_DOMAIN);
            }
            Session session = Database.getSession();
            try {
                domain = (Domain) session.load(Domain.class, rsTask.getDomain());
            } catch (Exception e) {
                logger.error("Unable to load the domain {}.", rsTask.getDomain());
                throw new NetshotBadRequestException("Invalid domain",
                        NetshotBadRequestException.NETSHOT_INVALID_DOMAIN);
            } finally {
                session.close();
            }
            StringBuffer target = new StringBuffer();
            target.append("{");
            for (Network4Address subnet : subnets) {
                if (target.length() > 1) {
                    target.append(", ");
                }
                target.append(subnet.getPrefix());
            }
            target.append("}");
            task = new ScanSubnetsTask(subnets, domain, rsTask.getComments(), target.toString(), userName);
        } else if (rsTask.getType().equals("PurgeDatabaseTask")) {
            logger.trace("Adding a PurgeDatabaseTask");
            if (rsTask.getDaysToPurge() < 2) {
                logger.error(String.format("Invalid number of days %d for the PurgeDatabaseTask task.",
                        rsTask.getDaysToPurge()));
                throw new NetshotBadRequestException("Invalid number of days.",
                        NetshotBadRequestException.NETSHOT_INVALID_TASK);
            }
            int configDays = rsTask.getConfigDaysToPurge();
            int configSize = rsTask.getConfigSizeToPurge();
            int configKeepDays = rsTask.getConfigKeepDays();
            if (configDays == -1) {
                configSize = 0;
                configKeepDays = 0;
            } else if (configDays <= 3) {
                logger.error("The number of days of configurations to purge must be greater than 3.");
                throw new NetshotBadRequestException(
                        "The number of days of configurations to purge must be greater than 3.",
                        NetshotBadRequestException.NETSHOT_INVALID_TASK);
            } else {
                if (configSize < 0) {
                    logger.error("The configuration size limit can't be negative.");
                    throw new NetshotBadRequestException("The limit on the configuration size can't be negative.",
                            NetshotBadRequestException.NETSHOT_INVALID_TASK);
                }
                if (configKeepDays < 0) {
                    logger.error("The interval of days between configurations to keep can't be negative.");
                    throw new NetshotBadRequestException(
                            "The number of days of configurations to purge can't be negative.",
                            NetshotBadRequestException.NETSHOT_INVALID_TASK);
                }
                if (configDays <= configKeepDays) {
                    logger.error(
                            "The number of days of configurations to purge must be greater than the number of days between two successive configurations to keep.");
                    throw new NetshotBadRequestException(
                            "The number of days of configurations to purge must be greater than the number of days between two successive configurations to keep.",
                            NetshotBadRequestException.NETSHOT_INVALID_TASK);
                }
            }
            task = new PurgeDatabaseTask(rsTask.getComments(), userName, rsTask.getDaysToPurge(), configDays,
                    configSize, configKeepDays);
        } else {
            logger.error("User posted an invalid task type '{}'.", rsTask.getType());
            throw new NetshotBadRequestException("Invalid task type.",
                    NetshotBadRequestException.NETSHOT_INVALID_TASK);
        }
        if (rsTask.getScheduleReference() != null) {
            task.setScheduleReference(rsTask.getScheduleReference());
            task.setScheduleType(rsTask.getScheduleType());
            if (task.getScheduleType() == ScheduleType.AT) {
                Calendar inOneMinute = Calendar.getInstance();
                inOneMinute.add(Calendar.MINUTE, 1);
                if (task.getScheduleReference().before(inOneMinute.getTime())) {
                    logger.error("The schedule for the task occurs in less than one minute ({} vs {}).",
                            task.getScheduleReference(), inOneMinute.getTime());
                    throw new NetshotBadRequestException("The schedule occurs in the past.",
                            NetshotBadRequestException.NETSHOT_INVALID_TASK);
                }
            }
        }
        try {
            TaskManager.addTask(task);
        } catch (HibernateException e) {
            logger.error("Unable to add the task.", e);
            throw new NetshotBadRequestException("Unable to add the task to the database.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } catch (SchedulerException e) {
            logger.error("Unable to schedule the task.", e);
            throw new NetshotBadRequestException("Unable to schedule the task.",
                    NetshotBadRequestException.NETSHOT_SCHEDULE_ERROR);
        }
        return task;
    }

    /**
     * The Class RsConfigChange.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsConfigChange {

        /** The device name. */
        private String deviceName;

        /** The device id. */
        private long deviceId;

        /** The old change date. */
        private Date oldChangeDate;

        /** The new change date. */
        private Date newChangeDate;

        /** The author. */
        private String author;

        /** The old id. */
        private long oldId = 0L;

        /** The new id. */
        private long newId = 0L;

        /**
         * Gets the device name.
         *
         * @return the device name
         */
        @XmlElement
        public String getDeviceName() {
            return deviceName;
        }

        /**
         * Sets the device name.
         *
         * @param deviceName the new device name
         */
        public void setDeviceName(String deviceName) {
            this.deviceName = deviceName;
        }

        /**
         * Gets the device id.
         *
         * @return the device id
         */
        @XmlElement
        public long getDeviceId() {
            return deviceId;
        }

        /**
         * Sets the device id.
         *
         * @param deviceId the new device id
         */
        public void setDeviceId(long deviceId) {
            this.deviceId = deviceId;
        }

        /**
         * Gets the author.
         *
         * @return the author
         */
        @XmlElement
        public String getAuthor() {
            return author;
        }

        /**
         * Sets the author.
         *
         * @param author the new author
         */
        public void setAuthor(String author) {
            this.author = author;
        }

        /**
         * Gets the old change date.
         *
         * @return the old change date
         */
        @XmlElement
        public Date getOldChangeDate() {
            return oldChangeDate;
        }

        /**
         * Sets the old change date.
         *
         * @param oldChangeDate the new old change date
         */
        public void setOldChangeDate(Date oldChangeDate) {
            this.oldChangeDate = oldChangeDate;
        }

        /**
         * Gets the new change date.
         *
         * @return the new change date
         */
        @XmlElement
        public Date getNewChangeDate() {
            return newChangeDate;
        }

        /**
         * Sets the new change date.
         *
         * @param newChangeDate the new new change date
         */
        public void setNewChangeDate(Date newChangeDate) {
            this.newChangeDate = newChangeDate;
        }

        /**
         * Gets the old id.
         *
         * @return the old id
         */
        @XmlElement
        public long getOldId() {
            return oldId;
        }

        /**
         * Sets the old id.
         *
         * @param oldId the new old id
         */
        public void setOldId(long oldId) {
            this.oldId = oldId;
        }

        /**
         * Gets the new id.
         *
         * @return the new id
         */
        @XmlElement
        public long getNewId() {
            return newId;
        }

        /**
         * Sets the new id.
         *
         * @param newId the new new id
         */
        public void setNewId(long newId) {
            this.newId = newId;
        }
    }

    /**
     * The Class RsChangeCriteria.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsChangeCriteria {

        /** The from date. */
        private Date fromDate;

        /** The to date. */
        private Date toDate;

        /**
         * Instantiates a new rs change criteria.
         */
        public RsChangeCriteria() {
            this.toDate = new Date();
            Calendar c = Calendar.getInstance();
            c.setTime(this.toDate);
            c.add(Calendar.DAY_OF_MONTH, -1);
            this.fromDate = c.getTime();
        }

        /**
         * Gets the from date.
         *
         * @return the from date
         */
        @XmlElement
        public Date getFromDate() {
            return fromDate;
        }

        /**
         * Sets the from date.
         *
         * @param fromDate the new from date
         */
        public void setFromDate(Date fromDate) {
            this.fromDate = fromDate;
        }

        /**
         * Gets the to date.
         *
         * @return the to date
         */
        @XmlElement
        public Date getToDate() {
            return toDate;
        }

        /**
         * Sets the to date.
         *
         * @param toDate the new to date
         */
        public void setToDate(Date toDate) {
            this.toDate = toDate;
        }
    }

    /**
     * Gets the changes.
     *
     * @param request the request
     * @param criteria the criteria
     * @return the changes
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("changes")
    @RolesAllowed("readonly")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsConfigChange> getChanges(RsChangeCriteria criteria) throws WebApplicationException {
        logger.debug("REST request, config changes.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsConfigChange> changes = session.createQuery(
                    "select c.id as newId, c.changeDate as newChangeDate, c.device.id as deviceId, c.author as author, c.device.name as deviceName from Config c where c.changeDate >= :start and c.changeDate <= :end")
                    .setTimestamp("start", criteria.fromDate).setTimestamp("end", criteria.toDate)
                    .setResultTransformer(Transformers.aliasToBean(RsConfigChange.class)).list();
            return changes;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the devices", e);
            throw new NetshotBadRequestException("Unable to fetch the devices",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the policies.
     *
     * @param request the request
     * @return the policies
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("policies")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<Policy> getPolicies() throws WebApplicationException {
        logger.debug("REST request, get policies.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<Policy> policies = session.createQuery("from Policy p left join fetch p.targetGroup").list();
            return policies;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the policies.", e);
            throw new NetshotBadRequestException("Unable to fetch the policies",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the policy rules.
     *
     * @param request the request
     * @param id the id
     * @return the policy rules
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("rules/policy/{id}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<Rule> getPolicyRules(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, get rules for policy {}.", id);
        Session session = Database.getSession();
        try {
            Policy policy = (Policy) session.load(Policy.class, id);
            if (policy == null) {
                logger.error("Invalid policy.");
                throw new NetshotBadRequestException("Invalid policy",
                        NetshotBadRequestException.NETSHOT_INVALID_POLICY);
            }
            List<Rule> rules = new ArrayList<Rule>();
            rules.addAll(policy.getRules());
            return rules;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the rules.", e);
            throw new NetshotBadRequestException("Unable to fetch the rules",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsPolicy.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsPolicy {

        /** The id. */
        private long id = 0;

        /** The name. */
        private String name = "";

        /** The group. */
        private long group = 0;

        /**
         * Instantiates a new rs policy.
         */
        public RsPolicy() {

        }

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Gets the name.
         *
         * @return the name
         */
        @XmlElement
        public String getName() {
            return name;
        }

        /**
         * Sets the name.
         *
         * @param name the new name
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * Gets the group.
         *
         * @return the group
         */
        @XmlElement
        public long getGroup() {
            return group;
        }

        /**
         * Sets the group.
         *
         * @param group the new group
         */
        public void setGroup(long group) {
            this.group = group;
        }
    }

    /**
     * Adds the policy.
     *
     * @param request the request
     * @param rsPolicy the rs policy
     * @return the policy
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("policies")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Policy addPolicy(RsPolicy rsPolicy) throws WebApplicationException {
        logger.debug("REST request, add policy.");
        String name = rsPolicy.getName().trim();
        if (name.isEmpty()) {
            logger.warn("User posted an empty policy name.");
            throw new NetshotBadRequestException("Invalid policy name.",
                    NetshotBadRequestException.NETSHOT_INVALID_POLICY_NAME);
        }
        Policy policy;
        Session session = Database.getSession();
        try {
            session.beginTransaction();

            DeviceGroup group = null;
            if (rsPolicy.getGroup() != -1) {
                group = (DeviceGroup) session.load(DeviceGroup.class, rsPolicy.getGroup());
            }

            policy = new Policy(name, group);

            session.save(policy);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The posted group doesn't exist", e);
            throw new NetshotBadRequestException("Invalid group", NetshotBadRequestException.NETSHOT_INVALID_GROUP);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the new policy.", e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A policy with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_POLICY);
            }
            throw new NetshotBadRequestException("Unable to add the policy to the database",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
        return policy;
    }

    /**
     * Delete policy.
     *
     * @param request the request
     * @param id the id
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("policies/{id}")
    @RolesAllowed("readwrite")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deletePolicy(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete policy {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            Policy policy = (Policy) session.load(Policy.class, id);
            session.delete(policy);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The policy {} to be deleted doesn't exist.", id, e);
            throw new NetshotBadRequestException("The policy doesn't exist.",
                    NetshotBadRequestException.NETSHOT_INVALID_POLICY);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to delete the policy {}.", id, e);
            throw new NetshotBadRequestException("Unable to delete the policy",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Sets the policy.
     *
     * @param request the request
     * @param id the id
     * @param rsPolicy the rs policy
     * @return the policy
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("policies/{id}")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Policy setPolicy(@PathParam("id") Long id, RsPolicy rsPolicy) throws WebApplicationException {
        logger.debug("REST request, edit policy {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            Policy policy = (Policy) session.get(Policy.class, id);
            if (policy == null) {
                logger.error("Unable to find the policy {} to be edited.", id);
                throw new NetshotBadRequestException("Unable to find this policy.",
                        NetshotBadRequestException.NETSHOT_INVALID_POLICY);
            }

            String name = rsPolicy.getName().trim();
            if (name.isEmpty()) {
                logger.warn("User posted an empty policy name.");
                throw new NetshotBadRequestException("Invalid policy name.",
                        NetshotBadRequestException.NETSHOT_INVALID_POLICY_NAME);
            }
            policy.setName(name);

            if (policy.getTargetGroup() != null && policy.getTargetGroup().getId() != rsPolicy.getGroup()) {
                session.createQuery(
                        "delete CheckResult cr where cr.key.rule in (select r from Rule r where r.policy = :id)")
                        .setLong("id", policy.getId()).executeUpdate();
            }
            DeviceGroup group = null;
            if (rsPolicy.getGroup() != -1) {
                group = (DeviceGroup) session.load(DeviceGroup.class, rsPolicy.getGroup());
            }
            policy.setTargetGroup(group);

            session.update(policy);
            session.getTransaction().commit();
            return policy;
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("Unable to find the group {} to be assigned to the policy {}.", rsPolicy.getGroup(), id,
                    e);
            throw new NetshotBadRequestException("Unable to find the group.",
                    NetshotBadRequestException.NETSHOT_INVALID_GROUP);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to save the policy {}.", id, e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A policy with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_POLICY);
            }
            throw new NetshotBadRequestException("Unable to save the policy.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } catch (WebApplicationException e) {
            session.getTransaction().rollback();
            throw e;
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsRule.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsRule {

        /** The id. */
        private long id = 0;

        /** The name. */
        private String name = null;

        /** The type. */
        private String type = "";

        /** The script. */
        private String script = null;

        /** The policy. */
        private long policy = 0;

        /** The enabled. */
        private boolean enabled = false;

        /** The exemptions. */
        private Map<Long, Date> exemptions = new HashMap<Long, Date>();

        private String text = null;
        private Boolean regExp;
        private String context = null;
        private String driver = null;
        private String field = null;
        private Boolean anyBlock;
        private Boolean matchAll;
        private Boolean invert;

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public Long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Gets the name.
         *
         * @return the name
         */
        @XmlElement
        public String getName() {
            return name;
        }

        /**
         * Sets the name.
         *
         * @param name the new name
         */
        public void setName(String name) {
            this.name = name;
        }

        @XmlElement
        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        /**
         * Gets the script.
         *
         * @return the script
         */
        @XmlElement
        public String getScript() {
            return script;
        }

        /**
         * Sets the script.
         *
         * @param script the new script
         */
        public void setScript(String script) {
            this.script = script;
        }

        /**
         * Gets the policy.
         *
         * @return the policy
         */
        @XmlElement
        public Long getPolicy() {
            return policy;
        }

        /**
         * Checks if is enabled.
         *
         * @return true, if is enabled
         */
        @XmlElement
        public boolean isEnabled() {
            return enabled;
        }

        /**
         * Sets the enabled.
         *
         * @param enabled the new enabled
         */
        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }

        /**
         * Sets the policy.
         *
         * @param policy the new policy
         */
        public void setPolicy(long policy) {
            this.policy = policy;
        }

        /**
         * Gets the exemptions.
         *
         * @return the exemptions
         */
        @XmlElement
        public Map<Long, Date> getExemptions() {
            return exemptions;
        }

        /**
         * Sets the exemptions.
         *
         * @param exemptions the exemptions
         */
        public void setExemptions(Map<Long, Date> exemptions) {
            this.exemptions = exemptions;
        }

        @XmlElement
        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        @XmlElement
        public Boolean isRegExp() {
            return regExp;
        }

        public void setRegExp(Boolean regExp) {
            this.regExp = regExp;
        }

        @XmlElement
        public String getContext() {
            return context;
        }

        public void setContext(String context) {
            this.context = context;
        }

        @XmlElement
        public String getField() {
            return field;
        }

        public void setField(String field) {
            this.field = field;
        }

        @XmlElement
        public String getDriver() {
            return driver;
        }

        public void setDriver(String driver) {
            this.driver = driver;
        }

        @XmlElement
        public Boolean isInvert() {
            return invert;
        }

        public void setInvert(Boolean invert) {
            this.invert = invert;
        }

        @XmlElement
        public Boolean isAnyBlock() {
            return anyBlock;
        }

        public void setAnyBlock(Boolean anyBlock) {
            this.anyBlock = anyBlock;
        }

        @XmlElement
        public Boolean isMatchAll() {
            return matchAll;
        }

        public void setMatchAll(Boolean matchAll) {
            this.matchAll = matchAll;
        }
    }

    /**
     * Adds the js rule.
     *
     * @param request the request
     * @param rsRule the rs rule
     * @return the rule
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("rules")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Rule addRule(RsRule rsRule) throws WebApplicationException {
        logger.debug("REST request, add rule.");
        if (rsRule.getName() == null || rsRule.getName().trim().isEmpty()) {
            logger.warn("User posted an empty rule name.");
            throw new NetshotBadRequestException("Invalid rule name.",
                    NetshotBadRequestException.NETSHOT_INVALID_RULE_NAME);
        }
        String name = rsRule.getName().trim();

        Session session = Database.getSession();
        try {
            session.beginTransaction();

            Policy policy = (Policy) session.load(Policy.class, rsRule.getPolicy());

            Rule rule;
            if (".TextRule".equals(rsRule.getType())) {
                rule = new TextRule(name, policy);
            } else {
                rule = new JavaScriptRule(name, policy);
            }

            session.save(rule);
            session.getTransaction().commit();
            return rule;
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The posted policy doesn't exist.", e);
            throw new NetshotBadRequestException("Invalid policy.",
                    NetshotBadRequestException.NETSHOT_INVALID_POLICY);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the new rule.", e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A rule with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_RULE);
            }
            throw new NetshotBadRequestException("Unable to add the rule to the database",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Sets the rule.
     *
     * @param request the request
     * @param id the id
     * @param rsRule the rs rule
     * @return the rule
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("rules/{id}")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Rule setRule(@PathParam("id") Long id, RsRule rsRule) throws WebApplicationException {
        logger.debug("REST request, edit rule {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            Rule rule = (Rule) session.get(Rule.class, id);
            if (rule == null) {
                logger.error("Unable to find the rule {} to be edited.", id);
                throw new NetshotBadRequestException("Unable to find this rule.",
                        NetshotBadRequestException.NETSHOT_INVALID_RULE);
            }

            if (rsRule.getName() != null) {
                String name = rsRule.getName().trim();
                if (name.isEmpty()) {
                    logger.warn("User posted an empty rule name.");
                    throw new NetshotBadRequestException("Invalid rule name.",
                            NetshotBadRequestException.NETSHOT_INVALID_RULE_NAME);
                }
                rule.setName(name);
            }
            rule.setEnabled(rsRule.isEnabled());

            Map<Long, Date> postedExemptions = new HashMap<Long, Date>();
            postedExemptions.putAll(rsRule.getExemptions());
            Iterator<Exemption> i = rule.getExemptions().iterator();
            while (i.hasNext()) {
                Exemption exemption = i.next();
                Long deviceId = exemption.getDevice().getId();
                if (postedExemptions.containsKey(deviceId)) {
                    exemption.setExpirationDate(postedExemptions.get(deviceId));
                    postedExemptions.remove(deviceId);
                } else {
                    i.remove();
                }
            }
            for (Map.Entry<Long, Date> postedExemption : postedExemptions.entrySet()) {
                Device device = (Device) session.load(Device.class, postedExemption.getKey());
                Exemption exemption = new Exemption(rule, device, postedExemption.getValue());
                rule.addExemption(exemption);
            }

            if (rule instanceof JavaScriptRule) {
                if (rsRule.getScript() != null) {
                    String script = rsRule.getScript().trim();
                    ((JavaScriptRule) rule).setScript(script);
                }
            } else if (rule instanceof TextRule) {
                if (rsRule.getText() != null) {
                    ((TextRule) rule).setText(rsRule.getText());
                }
                if (rsRule.isRegExp() != null) {
                    ((TextRule) rule).setRegExp(rsRule.isRegExp());
                }
                if (rsRule.getContext() != null) {
                    ((TextRule) rule).setContext(rsRule.getContext());
                }
                if (rsRule.getField() != null) {
                    ((TextRule) rule).setField(rsRule.getField());
                }
                if (rsRule.getDriver() != null) {
                    ((TextRule) rule).setDeviceDriver(rsRule.getDriver());
                }
                if (rsRule.isInvert() != null) {
                    ((TextRule) rule).setInvert(rsRule.isInvert());
                }
                if (rsRule.isMatchAll() != null) {
                    ((TextRule) rule).setMatchAll(rsRule.isMatchAll());
                }
                if (rsRule.isAnyBlock() != null) {
                    ((TextRule) rule).setAnyBlock(rsRule.isAnyBlock());
                }
            }

            session.update(rule);
            session.getTransaction().commit();
            return rule;
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the new rule.", e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A rule with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_RULE);
            }
            throw new NetshotBadRequestException("Unable to save the rule.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } catch (WebApplicationException e) {
            session.getTransaction().rollback();
            throw e;
        } finally {
            session.close();
        }
    }

    /**
     * Delete rule.
     *
     * @param request the request
     * @param id the id
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("rules/{id}")
    @RolesAllowed("readwrite")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deleteRule(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete rule {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            Rule rule = (Rule) session.load(Rule.class, id);
            session.delete(rule);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The rule {} to be deleted doesn't exist.", id, e);
            throw new NetshotBadRequestException("The rule doesn't exist.",
                    NetshotBadRequestException.NETSHOT_INVALID_RULE);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to delete the rule {}.", id, e);
            throw new NetshotBadRequestException("Unable to delete the rule.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsJsRuleTest.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsRuleTest extends RsRule {

        /** The device. */
        private long device = 0;

        /**
         * Gets the device.
         *
         * @return the device
         */
        @XmlElement
        public long getDevice() {
            return device;
        }

        /**
         * Sets the device.
         *
         * @param device the new device
         */
        public void setDevice(long device) {
            this.device = device;
        }

    }

    /**
     * The Class RsRuleTestResult.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsRuleTestResult {

        /** The result. */
        private CheckResult.ResultOption result;

        /** The script error. */
        private String scriptError;

        /**
         * Gets the result.
         *
         * @return the result
         */
        @XmlElement
        public CheckResult.ResultOption getResult() {
            return result;
        }

        /**
         * Sets the result.
         *
         * @param result the new result
         */
        public void setResult(CheckResult.ResultOption result) {
            this.result = result;
        }

        /**
         * Gets the script error.
         *
         * @return the script error
         */
        @XmlElement
        public String getScriptError() {
            return scriptError;
        }

        /**
         * Sets the script error.
         *
         * @param scriptError the new script error
         */
        public void setScriptError(String scriptError) {
            this.scriptError = scriptError;
        }

    }

    /**
     * Test js rule.
     *
     * @param request the request
     * @param rsRule the rs rule
     * @return the rs js rule test result
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("rules/test")
    @RolesAllowed("readonly")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public RsRuleTestResult testRule(RsRuleTest rsRule) throws WebApplicationException {
        logger.debug("REST request, rule test.");
        Device device;
        Session session = Database.getSession();
        try {
            device = (Device) session.createQuery("from Device d join fetch d.lastConfig where d.id = :id")
                    .setLong("id", rsRule.getDevice()).uniqueResult();
            if (device == null) {
                logger.warn("Unable to find the device {}.", rsRule.getDevice());
                throw new NetshotBadRequestException("Unable to find the device.",
                        NetshotBadRequestException.NETSHOT_INVALID_DEVICE);
            }

            Rule rule;

            if (".TextRule".equals(rsRule.getType())) {
                TextRule txRule = new TextRule("TEST", null);
                txRule.setDeviceDriver(rsRule.getDriver());
                txRule.setField(rsRule.getField());
                txRule.setInvert(rsRule.isInvert());
                txRule.setContext(rsRule.getContext());
                txRule.setRegExp(rsRule.isRegExp());
                txRule.setText(rsRule.getText());
                txRule.setAnyBlock(rsRule.isAnyBlock());
                txRule.setMatchAll(rsRule.isMatchAll());
                rule = txRule;
            } else {
                JavaScriptRule jsRule = new JavaScriptRule("TEST", null);
                jsRule.setScript(rsRule.getScript());
                rule = jsRule;
            }

            RsRuleTestResult result = new RsRuleTestResult();

            rule.setEnabled(true);
            rule.check(device, session);
            result.setResult(rule.getCheckResults().iterator().next().getResult());
            result.setScriptError(rule.getPlainLog());

            return result;
        } catch (Exception e) {
            logger.error("Unable to retrieve the device {}.", rsRule.getDevice(), e);
            throw new NetshotBadRequestException("Unable to retrieve the device.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsLightExemptedDevice.
     */
    @XmlRootElement
    @XmlAccessorType(value = XmlAccessType.NONE)
    public static class RsLightExemptedDevice extends RsLightDevice {

        /** The expiration date. */
        private Date expirationDate;

        /**
         * Gets the expiration date.
         *
         * @return the expiration date
         */
        @XmlElement
        public Date getExpirationDate() {
            return expirationDate;
        }

        /**
         * Sets the expiration date.
         *
         * @param expirationDate the new expiration date
         */
        public void setExpirationDate(Date expirationDate) {
            this.expirationDate = expirationDate;
        }

    }

    /**
     * Gets the exempted devices.
     *
     * @param request the request
     * @param id the id
     * @return the exempted devices
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("devices/rule/{id}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsLightExemptedDevice> getExemptedDevices(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, get exemptions for rule {}.", id);
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsLightExemptedDevice> exemptions = session.createQuery(DEVICELIST_BASEQUERY
                    + ", e.expirationDate as expirationDate from Exemption e join e.key.device d where e.key.rule.id = :id")
                    .setLong("id", id).setResultTransformer(Transformers.aliasToBean(RsLightExemptedDevice.class))
                    .list();
            return exemptions;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the exemptions.", e);
            throw new NetshotBadRequestException("Unable to fetch the exemptions",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsDeviceRule.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsDeviceRule {

        /** The id. */
        private long id = 0;

        /** The rule name. */
        private String ruleName = "";

        /** The policy name. */
        private String policyName = "";

        /** The result. */
        private CheckResult.ResultOption result;

        /** The comment. */
        private String comment = "";

        /** The check date. */
        private Date checkDate;

        /** The expiration date. */
        private Date expirationDate;

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public Long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Gets the rule name.
         *
         * @return the rule name
         */
        @XmlElement
        public String getRuleName() {
            return ruleName;
        }

        /**
         * Sets the rule name.
         *
         * @param ruleName the new rule name
         */
        public void setRuleName(String ruleName) {
            this.ruleName = ruleName;
        }

        /**
         * Gets the policy name.
         *
         * @return the policy name
         */
        @XmlElement
        public String getPolicyName() {
            return policyName;
        }

        /**
         * Sets the policy name.
         *
         * @param policyName the new policy name
         */
        public void setPolicyName(String policyName) {
            this.policyName = policyName;
        }

        /**
         * Gets the result.
         *
         * @return the result
         */
        @XmlElement
        public CheckResult.ResultOption getResult() {
            return result;
        }

        /**
         * Sets the result.
         *
         * @param result the new result
         */
        public void setResult(CheckResult.ResultOption result) {
            this.result = result;
        }

        /**
         * Gets the check date.
         *
         * @return the check date
         */
        @XmlElement
        public Date getCheckDate() {
            return checkDate;
        }

        /**
         * Sets the check date.
         *
         * @param checkDate the new check date
         */
        public void setCheckDate(Date checkDate) {
            this.checkDate = checkDate;
        }

        /**
         * Gets the expiration date.
         *
         * @return the expiration date
         */
        @XmlElement
        public Date getExpirationDate() {
            return expirationDate;
        }

        /**
         * Sets the expiration date.
         *
         * @param expirationDate the new expiration date
         */
        public void setExpirationDate(Date expirationDate) {
            this.expirationDate = expirationDate;
        }

        /**
         * Gets the comment.
         *
         * @return the comment
         */
        @XmlElement
        public String getComment() {
            return comment;
        }

        /**
         * Sets the comment.
         *
         * @param comment the new comment
         */
        public void setComment(String comment) {
            this.comment = comment;
        }

    }

    /**
     * Gets the device compliance.
     *
     * @param request the request
     * @param id the id
     * @return the device compliance
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("rules/device/{id}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsDeviceRule> getDeviceCompliance(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, get exemptions for rules {}.", id);
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsDeviceRule> rules = session.createQuery(
                    "select r.id as id, r.name as ruleName, p.name as policyName, cr.result as result, cr.checkDate as checkDate, cr.comment as comment, e.expirationDate as expirationDate from Rule r join r.policy p join p.targetGroup g join g.cachedDevices d1 with d1.id = :id left join r.checkResults cr with cr.key.device.id = :id left join r.exemptions e with e.key.device.id = :id")
                    .setLong("id", id).setResultTransformer(Transformers.aliasToBean(RsDeviceRule.class)).list();
            return rules;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the rules.", e);
            throw new NetshotBadRequestException("Unable to fetch the rules",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsConfigChangeNumberByDateStat.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsConfigChangeNumberByDateStat {

        /** The change count. */
        private long changeCount;

        /** The change day. */
        private Date changeDay;

        /**
         * Gets the change count.
         *
         * @return the change count
         */
        @XmlElement
        public long getChangeCount() {
            return changeCount;
        }

        /**
         * Sets the change count.
         *
         * @param changes the new change count
         */
        public void setChangeCount(long changes) {
            this.changeCount = changes;
        }

        /**
         * Gets the change day.
         *
         * @return the change day
         */
        @XmlElement
        public Date getChangeDay() {
            return changeDay;
        }

        /**
         * Sets the change day.
         *
         * @param date the new change day
         */
        public void setChangeDay(Date date) {
            this.changeDay = date;
        }

    }

    /**
     * Gets the last7 days changes by day stats.
     *
     * @param request the request
     * @return the last7 days changes by day stats
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("reports/last7dayschangesbyday")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsConfigChangeNumberByDateStat> getLast7DaysChangesByDayStats() throws WebApplicationException {
        logger.debug("REST request, get last 7 day changes by day stats.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsConfigChangeNumberByDateStat> stats = session.createQuery(
                    "select count(c) as changeCount, cast(cast(c.changeDate as date) as timestamp) as changeDay from Config c group by cast(c.changeDate as date) order by changeDate desc")
                    .setMaxResults(7)
                    .setResultTransformer(Transformers.aliasToBean(RsConfigChangeNumberByDateStat.class)).list();
            return stats;
        } catch (HibernateException e) {
            logger.error("Unable to get the stats.", e);
            throw new NetshotBadRequestException("Unable to get the stats",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsGroupConfigComplianceStat.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsGroupConfigComplianceStat {

        /** The group id. */
        private long groupId;

        /** The group name. */
        private String groupName;

        /** The compliant device count. */
        private long compliantDeviceCount;

        /** The device count. */
        private long deviceCount;

        /**
         * Gets the group id.
         *
         * @return the group id
         */
        @XmlElement
        public long getGroupId() {
            return groupId;
        }

        /**
         * Sets the group id.
         *
         * @param groupId the new group id
         */
        public void setGroupId(long groupId) {
            this.groupId = groupId;
        }

        /**
         * Gets the group name.
         *
         * @return the group name
         */
        @XmlElement
        public String getGroupName() {
            return groupName;
        }

        /**
         * Sets the group name.
         *
         * @param groupName the new group name
         */
        public void setGroupName(String groupName) {
            this.groupName = groupName;
        }

        /**
         * Gets the compliant device count.
         *
         * @return the compliant device count
         */
        @XmlElement
        public long getCompliantDeviceCount() {
            return compliantDeviceCount;
        }

        /**
         * Sets the compliant device count.
         *
         * @param compliantCount the new compliant device count
         */
        public void setCompliantDeviceCount(long compliantCount) {
            this.compliantDeviceCount = compliantCount;
        }

        /**
         * Gets the device count.
         *
         * @return the device count
         */
        @XmlElement
        public long getDeviceCount() {
            return deviceCount;
        }

        /**
         * Sets the device count.
         *
         * @param deviceCount the new device count
         */
        public void setDeviceCount(long deviceCount) {
            this.deviceCount = deviceCount;
        }
    }

    /**
     * Gets the group config compliance stats.
     *
     * @param request the request
     * @return the group config compliance stats
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("reports/groupconfigcompliancestats")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsGroupConfigComplianceStat> getGroupConfigComplianceStats() throws WebApplicationException {
        logger.debug("REST request, group config compliance stats.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsGroupConfigComplianceStat> stats = session.createQuery(
                    "select g.id as groupId, g.name as groupName, (select count(d) from g.cachedDevices d where d.status = :enabled and (select count(ccr.result) from d.complianceCheckResults ccr where ccr.result = :nonConforming) = 0) as compliantDeviceCount, (select count(d) from g.cachedDevices d where d.status = :enabled) as deviceCount from DeviceGroup g where g.hiddenFromReports <> true")
                    .setParameter("nonConforming", CheckResult.ResultOption.NONCONFORMING)
                    .setParameter("enabled", Device.Status.INPRODUCTION)
                    .setResultTransformer(Transformers.aliasToBean(RsGroupConfigComplianceStat.class)).list();
            return stats;
        } catch (HibernateException e) {
            logger.error("Unable to get the stats.", e);
            throw new NetshotBadRequestException("Unable to get the stats",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")
    public abstract static class RsHardwareSupportStat {
        private Date eoxDate;
        private long deviceCount;

        @XmlElement
        public Date getEoxDate() {
            return eoxDate;
        }

        public void setEoxDate(Date date) {
            this.eoxDate = date;
        }

        @XmlElement
        public long getDeviceCount() {
            return deviceCount;
        }

        public void setDeviceCount(long deviceCount) {
            this.deviceCount = deviceCount;
        }

    }

    @XmlType
    public static class RsHardwareSupportEoSStat extends RsHardwareSupportStat {

    }

    @XmlType
    public static class RsHardwareSupportEoLStat extends RsHardwareSupportStat {

    }

    @GET
    @Path("reports/hardwaresupportstats")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsHardwareSupportStat> getHardwareSupportStats() throws WebApplicationException {
        logger.debug("REST request, hardware support stats.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsHardwareSupportStat> eosStats = session.createQuery(
                    "select count(d) as deviceCount, d.eosDate AS eoxDate from Device d where d.status = :enabled group by d.eosDate")
                    .setParameter("enabled", Device.Status.INPRODUCTION)
                    .setResultTransformer(Transformers.aliasToBean(RsHardwareSupportEoSStat.class)).list();
            @SuppressWarnings("unchecked")
            List<RsHardwareSupportStat> eolStats = session.createQuery(
                    "select count(d) as deviceCount, d.eolDate AS eoxDate from Device d where d.status = :enabled group by d.eolDate")
                    .setParameter("enabled", Device.Status.INPRODUCTION)
                    .setResultTransformer(Transformers.aliasToBean(RsHardwareSupportEoLStat.class)).list();
            List<RsHardwareSupportStat> stats = new ArrayList<RsHardwareSupportStat>();
            stats.addAll(eosStats);
            stats.addAll(eolStats);
            return stats;
        } catch (HibernateException e) {
            logger.error("Unable to ge" + "" + "t the stats.", e);
            throw new NetshotBadRequestException("Unable to get the stats",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsGroupSoftwareComplianceStat.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsGroupSoftwareComplianceStat {

        /** The group id. */
        private long groupId;

        /** The group name. */
        private String groupName;

        /** The gold device count. */
        private long goldDeviceCount;

        /** The silver device count. */
        private long silverDeviceCount;

        /** The bronze device count. */
        private long bronzeDeviceCount;

        /** The device count. */
        private long deviceCount;

        /**
         * Gets the group id.
         *
         * @return the group id
         */
        @XmlElement
        public long getGroupId() {
            return groupId;
        }

        /**
         * Sets the group id.
         *
         * @param groupId the new group id
         */
        public void setGroupId(long groupId) {
            this.groupId = groupId;
        }

        /**
         * Gets the group name.
         *
         * @return the group name
         */
        @XmlElement
        public String getGroupName() {
            return groupName;
        }

        /**
         * Sets the group name.
         *
         * @param groupName the new group name
         */
        public void setGroupName(String groupName) {
            this.groupName = groupName;
        }

        /**
         * Gets the gold device count.
         *
         * @return the gold device count
         */
        @XmlElement
        public long getGoldDeviceCount() {
            return goldDeviceCount;
        }

        /**
         * Sets the gold device count.
         *
         * @param goldDeviceCount the new gold device count
         */
        public void setGoldDeviceCount(long goldDeviceCount) {
            this.goldDeviceCount = goldDeviceCount;
        }

        /**
         * Gets the silver device count.
         *
         * @return the silver device count
         */
        @XmlElement
        public long getSilverDeviceCount() {
            return silverDeviceCount;
        }

        /**
         * Sets the silver device count.
         *
         * @param silverDeviceCount the new silver device count
         */
        public void setSilverDeviceCount(long silverDeviceCount) {
            this.silverDeviceCount = silverDeviceCount;
        }

        /**
         * Gets the bronze device count.
         *
         * @return the bronze device count
         */
        @XmlElement
        public long getBronzeDeviceCount() {
            return bronzeDeviceCount;
        }

        /**
         * Sets the bronze device count.
         *
         * @param bronzeDeviceCount the new bronze device count
         */
        public void setBronzeDeviceCount(long bronzeDeviceCount) {
            this.bronzeDeviceCount = bronzeDeviceCount;
        }

        /**
         * Gets the device count.
         *
         * @return the device count
         */
        @XmlElement
        public long getDeviceCount() {
            return deviceCount;
        }

        /**
         * Sets the device count.
         *
         * @param deviceCount the new device count
         */
        public void setDeviceCount(long deviceCount) {
            this.deviceCount = deviceCount;
        }
    }

    /**
     * Gets the group software compliance stats.
     *
     * @param request the request
     * @return the group software compliance stats
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("reports/groupsoftwarecompliancestats")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsGroupSoftwareComplianceStat> getGroupSoftwareComplianceStats() throws WebApplicationException {
        logger.debug("REST request, group software compliance stats.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsGroupSoftwareComplianceStat> stats = session.createQuery(
                    "select g.id as groupId, g.name as groupName, (select count(d) from g.cachedDevices d where d.status = :enabled and d.softwareLevel = :gold) as goldDeviceCount, (select count(d) from g.cachedDevices d where d.status = :enabled and d.softwareLevel = :silver) as silverDeviceCount, (select count(d) from g.cachedDevices d where d.status = :enabled and d.softwareLevel = :bronze) as bronzeDeviceCount, (select count(d) from g.cachedDevices d where d.status = :enabled) as deviceCount from DeviceGroup g where g.hiddenFromReports <> true")
                    .setParameter("gold", ConformanceLevel.GOLD).setParameter("silver", ConformanceLevel.SILVER)
                    .setParameter("bronze", ConformanceLevel.BRONZE)
                    .setParameter("enabled", Device.Status.INPRODUCTION)
                    .setResultTransformer(Transformers.aliasToBean(RsGroupSoftwareComplianceStat.class)).list();
            return stats;
        } catch (HibernateException e) {
            logger.error("Unable to get the stats.", e);
            throw new NetshotBadRequestException("Unable to get the stats",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsLightPolicyRuleDevice.
     */
    @XmlRootElement
    @XmlAccessorType(value = XmlAccessType.NONE)
    public static class RsLightPolicyRuleDevice extends RsLightDevice {

        /** The rule name. */
        private String ruleName;

        /** The policy name. */
        private String policyName;

        /** The check date. */
        private Date checkDate;

        /** The result. */
        private ResultOption result;

        /**
         * Gets the rule name.
         *
         * @return the rule name
         */
        @XmlElement
        public String getRuleName() {
            return ruleName;
        }

        /**
         * Gets the policy name.
         *
         * @return the policy name
         */
        @XmlElement
        public String getPolicyName() {
            return policyName;
        }

        /**
         * Gets the check date.
         *
         * @return the check date
         */
        @XmlElement
        public Date getCheckDate() {
            return checkDate;
        }

        /**
         * Gets the result.
         *
         * @return the result
         */
        @XmlElement
        public ResultOption getResult() {
            return result;
        }

        /**
         * Sets the rule name.
         *
         * @param ruleName the new rule name
         */
        public void setRuleName(String ruleName) {
            this.ruleName = ruleName;
        }

        /**
         * Sets the policy name.
         *
         * @param policyName the new policy name
         */
        public void setPolicyName(String policyName) {
            this.policyName = policyName;
        }

        /**
         * Sets the check date.
         *
         * @param checkDate the new check date
         */
        public void setCheckDate(Date checkDate) {
            this.checkDate = checkDate;
        }

        /**
         * Sets the result.
         *
         * @param result the new result
         */
        public void setResult(ResultOption result) {
            this.result = result;
        }
    }

    /**
     * Gets the group config non compliant devices.
     *
     * @param request the request
     * @param id the id
     * @return the group config non compliant devices
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("reports/groupconfignoncompliantdevices/{id}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsLightPolicyRuleDevice> getGroupConfigNonCompliantDevices(@PathParam("id") Long id)
            throws WebApplicationException {
        logger.debug("REST request, group config non compliant devices.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<RsLightPolicyRuleDevice> devices = session.createQuery(DEVICELIST_BASEQUERY
                    + ", p.name as policyName, r.name as ruleName, ccr.checkDate as checkDate, ccr.result as result from Device d join d.ownerGroups g join d.complianceCheckResults ccr join ccr.key.rule r join r.policy p where g.id = :id and ccr.result = :nonConforming and d.status = :enabled")
                    .setLong("id", id).setParameter("nonConforming", CheckResult.ResultOption.NONCONFORMING)
                    .setParameter("enabled", Device.Status.INPRODUCTION)
                    .setResultTransformer(Transformers.aliasToBean(RsLightPolicyRuleDevice.class)).list();
            return devices;
        } catch (HibernateException e) {
            logger.error("Unable to get the devices.", e);
            throw new NetshotBadRequestException("Unable to get the stats",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    @GET
    @Path("reports/hardwaresupportdevices/{type}/{date}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsLightDevice> getHardwareStatusDevices(@PathParam("type") String type,
            @PathParam("date") Long date) throws WebApplicationException {
        logger.debug("REST request, EoX devices by type and date.");
        if (!type.equals("eol") && !type.equals("eos")) {
            logger.error("Invalid requested EoX type.");
            throw new NetshotBadRequestException("Unable to get the stats",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        }
        Date eoxDate = new Date(date);
        Session session = Database.getSession();
        try {
            if (date == 0) {
                @SuppressWarnings("unchecked")
                List<RsLightDevice> devices = session
                        .createQuery(DEVICELIST_BASEQUERY + "from Device d where d." + type
                                + "Date is null and d.status = :enabled")
                        .setParameter("enabled", Device.Status.INPRODUCTION)
                        .setResultTransformer(Transformers.aliasToBean(RsLightDevice.class)).list();
                return devices;
            } else {
                @SuppressWarnings("unchecked")
                List<RsLightDevice> devices = session
                        .createQuery(DEVICELIST_BASEQUERY + "from Device d where date(d." + type
                                + "Date) = :eoxDate and d.status = :enabled")
                        .setDate("eoxDate", eoxDate).setParameter("enabled", Device.Status.INPRODUCTION)
                        .setResultTransformer(Transformers.aliasToBean(RsLightDevice.class)).list();
                return devices;
            }
        } catch (HibernateException e) {
            logger.error("Unable to get the devices.", e);
            throw new NetshotBadRequestException("Unable to get the stats",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Gets the hardware rules.
     *
     * @param request the request
     * @return the harware rules
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("hardwarerules")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<HardwareRule> getHardwareRules() throws WebApplicationException {
        logger.debug("REST request, hardware rules.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<HardwareRule> rules = session.createQuery("from HardwareRule r left join fetch r.targetGroup g")
                    .list();
            return rules;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the hardware rules.", e);
            throw new NetshotBadRequestException("Unable to fetch the hardware rules.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsHardwareRule.
     */
    @XmlRootElement
    @XmlAccessorType(value = XmlAccessType.NONE)
    public static class RsHardwareRule {

        /** The id. */
        private long id;

        /** The group. */
        private long group = -1;

        /** The device class name. */
        private String driver = "";

        /** The part number. */
        private String partNumber = "";

        private boolean partNumberRegExp = false;

        /** The family. */
        private String family = "";

        private boolean familyRegExp = false;

        private Date endOfSale = null;

        private Date endOfLife = null;

        @XmlElement
        public long getId() {
            return id;
        }

        public void setId(long id) {
            this.id = id;
        }

        @XmlElement
        public long getGroup() {
            return group;
        }

        public void setGroup(long group) {
            this.group = group;
        }

        @XmlElement
        public String getDriver() {
            return driver;
        }

        public void setDriver(String driver) {
            this.driver = driver;
        }

        @XmlElement
        public String getPartNumber() {
            return partNumber;
        }

        public void setPartNumber(String partNumber) {
            this.partNumber = partNumber;
        }

        @XmlElement
        public boolean isPartNumberRegExp() {
            return partNumberRegExp;
        }

        public void setPartNumberRegExp(boolean partNumberRegExp) {
            this.partNumberRegExp = partNumberRegExp;
        }

        @XmlElement
        public String getFamily() {
            return family;
        }

        public void setFamily(String family) {
            this.family = family;
        }

        @XmlElement
        public boolean isFamilyRegExp() {
            return familyRegExp;
        }

        public void setFamilyRegExp(boolean familyRegExp) {
            this.familyRegExp = familyRegExp;
        }

        @XmlElement(nillable = true)
        public Date getEndOfSale() {
            return endOfSale;
        }

        public void setEndOfSale(Date endOfSale) {
            this.endOfSale = endOfSale;
        }

        @XmlElement(nillable = true)
        public Date getEndOfLife() {
            return endOfLife;
        }

        public void setEndOfLife(Date endOfLife) {
            this.endOfLife = endOfLife;
        }
    }

    /**
     * Adds an hardware rule.
     *
     * @param request the request
     * @param rsRule the rs rule
     * @return the hardware rule
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("hardwarerules")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public HardwareRule addHardwareRule(RsHardwareRule rsRule) throws WebApplicationException {
        logger.debug("REST request, add hardware rule.");

        HardwareRule rule;
        Session session = Database.getSession();
        try {
            session.beginTransaction();

            DeviceGroup group = null;
            if (rsRule.getGroup() != -1) {
                group = (DeviceGroup) session.load(DeviceGroup.class, rsRule.getGroup());
            }

            String driver = rsRule.getDriver();
            if (DeviceDriver.getDriverByName(driver) == null) {
                driver = null;
            }

            rule = new HardwareRule(driver, group, rsRule.getFamily(), rsRule.isFamilyRegExp(),
                    rsRule.getPartNumber(), rsRule.isPartNumberRegExp(), rsRule.getEndOfSale(),
                    rsRule.getEndOfLife());

            session.save(rule);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The posted group doesn't exist", e);
            throw new NetshotBadRequestException("Invalid group", NetshotBadRequestException.NETSHOT_INVALID_GROUP);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the new rule.", e);
            throw new NetshotBadRequestException("Unable to add the rule to the database",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
        return rule;
    }

    /**
     * Delete software rule.
     *
     * @param request the request
     * @param id the id
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("hardwarerules/{id}")
    @RolesAllowed("readwrite")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deleteHardwareRule(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete hardware rule {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            HardwareRule rule = (HardwareRule) session.load(HardwareRule.class, id);
            session.delete(rule);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The rule {} to be deleted doesn't exist.", id, e);
            throw new NetshotBadRequestException("The rule doesn't exist.",
                    NetshotBadRequestException.NETSHOT_INVALID_RULE);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to delete the rule {}.", id, e);
            throw new NetshotBadRequestException("Unable to delete the rule.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Sets the hardware rule.
     *
     * @param request the request
     * @param id the id
     * @param rsRule the rs rule
     * @return the hardware rule
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("hardwarerules/{id}")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public HardwareRule setHardwareRule(@PathParam("id") Long id, RsHardwareRule rsRule)
            throws WebApplicationException {
        logger.debug("REST request, edit hardware rule {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            HardwareRule rule = (HardwareRule) session.get(HardwareRule.class, id);
            if (rule == null) {
                logger.error("Unable to find the rule {} to be edited.", id);
                throw new NetshotBadRequestException("Unable to find this rule.",
                        NetshotBadRequestException.NETSHOT_INVALID_RULE);
            }

            String driver = rsRule.getDriver();
            if (DeviceDriver.getDriverByName(driver) == null) {
                driver = null;
            }
            rule.setDriver(driver);

            DeviceGroup group = null;
            if (rsRule.getGroup() != -1) {
                group = (DeviceGroup) session.load(DeviceGroup.class, rsRule.getGroup());
            }
            rule.setTargetGroup(group);

            rule.setFamily(rsRule.getFamily());
            rule.setFamilyRegExp(rsRule.isFamilyRegExp());
            rule.setEndOfLife(rsRule.getEndOfLife());
            rule.setEndOfSale(rsRule.getEndOfSale());
            rule.setPartNumber(rsRule.getPartNumber());
            rule.setPartNumberRegExp(rsRule.isPartNumberRegExp());

            session.update(rule);
            session.getTransaction().commit();
            return rule;
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the rule.", e);
            throw new NetshotBadRequestException("Unable to save the rule.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } catch (WebApplicationException e) {
            session.getTransaction().rollback();
            throw e;
        } finally {
            session.close();
        }
    }

    /**
     * Gets the software rules.
     *
     * @param request the request
     * @return the software rules
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("softwarerules")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<SoftwareRule> getSoftwareRules() throws WebApplicationException {
        logger.debug("REST request, software rules.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<SoftwareRule> rules = session.createQuery("from SoftwareRule r left join fetch r.targetGroup g")
                    .list();
            return rules;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the software rules.", e);
            throw new NetshotBadRequestException("Unable to fetch the software rules.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsSoftwareRule.
     */
    @XmlRootElement
    @XmlAccessorType(value = XmlAccessType.NONE)
    public static class RsSoftwareRule {

        /** The id. */
        private long id;

        /** The group. */
        private long group = -1;

        /** The device class name. */
        private String driver = "";

        /** The version. */
        private String version = "";

        private boolean versionRegExp = false;

        /** The family. */
        private String family = "";

        private boolean familyRegExp = false;

        /** The level. */
        private SoftwareRule.ConformanceLevel level = ConformanceLevel.GOLD;

        /** The priority. */
        private double priority = -1;

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Gets the group.
         *
         * @return the group
         */
        @XmlElement
        public long getGroup() {
            return group;
        }

        /**
         * Sets the group.
         *
         * @param group the new group
         */
        public void setGroup(long group) {
            this.group = group;
        }

        /**
         * Gets the device class name.
         *
         * @return the device class name
         */
        @XmlElement
        public String getDriver() {
            return driver;
        }

        public void setDriver(String driver) {
            this.driver = driver;
        }

        /**
         * Gets the version.
         *
         * @return the version
         */
        @XmlElement
        public String getVersion() {
            return version;
        }

        /**
         * Sets the version.
         *
         * @param version the new version
         */
        public void setVersion(String version) {
            this.version = version;
        }

        /**
         * Gets the family.
         *
         * @return the family
         */
        @XmlElement
        public String getFamily() {
            return family;
        }

        /**
         * Sets the family.
         *
         * @param family the new family
         */
        public void setFamily(String family) {
            this.family = family;
        }

        /**
         * Gets the level.
         *
         * @return the level
         */
        @XmlElement
        public SoftwareRule.ConformanceLevel getLevel() {
            return level;
        }

        /**
         * Sets the level.
         *
         * @param level the new level
         */
        public void setLevel(SoftwareRule.ConformanceLevel level) {
            this.level = level;
        }

        /**
         * Gets the priority.
         *
         * @return the priority
         */
        @XmlElement
        public double getPriority() {
            return priority;
        }

        /**
         * Sets the priority.
         *
         * @param priority the new priority
         */
        public void setPriority(double priority) {
            this.priority = priority;
        }

        @XmlElement
        public boolean isVersionRegExp() {
            return versionRegExp;
        }

        public void setVersionRegExp(boolean versionRegExp) {
            this.versionRegExp = versionRegExp;
        }

        @XmlElement
        public boolean isFamilyRegExp() {
            return familyRegExp;
        }

        public void setFamilyRegExp(boolean familyRegExp) {
            this.familyRegExp = familyRegExp;
        }
    }

    /**
     * Adds the software rule.
     *
     * @param request the request
     * @param rsRule the rs rule
     * @return the software rule
     * @throws WebApplicationException the web application exception
     */
    @POST
    @Path("softwarerules")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public SoftwareRule addSoftwareRule(RsSoftwareRule rsRule) throws WebApplicationException {
        logger.debug("REST request, add software rule.");

        SoftwareRule rule;
        Session session = Database.getSession();
        try {
            session.beginTransaction();

            DeviceGroup group = null;
            if (rsRule.getGroup() != -1) {
                group = (DeviceGroup) session.load(DeviceGroup.class, rsRule.getGroup());
            }

            String driver = rsRule.getDriver();
            if (DeviceDriver.getDriverByName(driver) == null) {
                driver = null;
            }

            rule = new SoftwareRule(rsRule.getPriority(), group, driver, rsRule.getFamily(),
                    rsRule.isFamilyRegExp(), rsRule.getVersion(), rsRule.isVersionRegExp(), rsRule.getLevel());

            session.save(rule);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The posted group doesn't exist", e);
            throw new NetshotBadRequestException("Invalid group", NetshotBadRequestException.NETSHOT_INVALID_GROUP);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the new rule.", e);
            throw new NetshotBadRequestException("Unable to add the policy to the database",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
        return rule;
    }

    /**
     * Delete software rule.
     *
     * @param request the request
     * @param id the id
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("softwarerules/{id}")
    @RolesAllowed("readwrite")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deleteSoftwareRule(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete software rule {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            SoftwareRule rule = (SoftwareRule) session.load(SoftwareRule.class, id);
            session.delete(rule);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The rule {} to be deleted doesn't exist.", id, e);
            throw new NetshotBadRequestException("The rule doesn't exist.",
                    NetshotBadRequestException.NETSHOT_INVALID_RULE);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to delete the rule {}.", id, e);
            throw new NetshotBadRequestException("Unable to delete the rule.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Sets the software rule.
     *
     * @param request the request
     * @param id the id
     * @param rsRule the rs rule
     * @return the software rule
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("softwarerules/{id}")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public SoftwareRule setSoftwareRule(@PathParam("id") Long id, RsSoftwareRule rsRule)
            throws WebApplicationException {
        logger.debug("REST request, edit software rule {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            SoftwareRule rule = (SoftwareRule) session.get(SoftwareRule.class, id);
            if (rule == null) {
                logger.error("Unable to find the rule {} to be edited.", id);
                throw new NetshotBadRequestException("Unable to find this rule.",
                        NetshotBadRequestException.NETSHOT_INVALID_RULE);
            }

            String driver = rsRule.getDriver();
            if (DeviceDriver.getDriverByName(driver) == null) {
                driver = null;
            }
            rule.setDriver(driver);

            DeviceGroup group = null;
            if (rsRule.getGroup() != -1) {
                group = (DeviceGroup) session.load(DeviceGroup.class, rsRule.getGroup());
            }
            rule.setTargetGroup(group);

            rule.setFamily(rsRule.getFamily());
            rule.setFamilyRegExp(rsRule.isFamilyRegExp());
            rule.setVersion(rsRule.getVersion());
            rule.setVersionRegExp(rsRule.isVersionRegExp());
            rule.setPriority(rsRule.getPriority());
            rule.setLevel(rsRule.getLevel());

            session.update(rule);
            session.getTransaction().commit();
            return rule;
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the rule.", e);
            throw new NetshotBadRequestException("Unable to save the rule.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } catch (WebApplicationException e) {
            session.getTransaction().rollback();
            throw e;
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsLightSoftwareLevelDevice.
     */
    @XmlRootElement
    @XmlAccessorType(value = XmlAccessType.NONE)
    public static class RsLightSoftwareLevelDevice extends RsLightDevice {

        /** The software level. */
        private ConformanceLevel softwareLevel;

        /**
         * Gets the software level.
         *
         * @return the software level
         */
        @XmlElement
        public ConformanceLevel getSoftwareLevel() {
            return softwareLevel;
        }

        /**
         * Sets the software level.
         *
         * @param level the new software level
         */
        public void setSoftwareLevel(ConformanceLevel level) {
            this.softwareLevel = level;
        }
    }

    /**
     * Gets the group devices by software level.
     *
     * @param request the request
     * @param id the id
     * @param level the level
     * @return the group devices by software level
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("reports/groupdevicesbysoftwarelevel/{id}/{level}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsLightSoftwareLevelDevice> getGroupDevicesBySoftwareLevel(@PathParam("id") Long id,
            @PathParam("level") String level) throws WebApplicationException {
        logger.debug("REST request, group {} devices by software level {}.", id, level);
        Session session = Database.getSession();

        ConformanceLevel filterLevel = ConformanceLevel.UNKNOWN;
        for (ConformanceLevel l : ConformanceLevel.values()) {
            if (l.toString().equalsIgnoreCase(level)) {
                filterLevel = l;
                break;
            }
        }

        try {
            @SuppressWarnings("unchecked")
            List<RsLightSoftwareLevelDevice> devices = session.createQuery(DEVICELIST_BASEQUERY
                    + ", d.softwareLevel as softwareLevel from Device d join d.ownerGroups g where g.id = :id and d.softwareLevel = :level and d.status = :enabled")
                    .setLong("id", id).setParameter("level", filterLevel)
                    .setParameter("enabled", Device.Status.INPRODUCTION)
                    .setResultTransformer(Transformers.aliasToBean(RsLightSoftwareLevelDevice.class)).list();
            return devices;
        } catch (HibernateException e) {
            logger.error("Unable to get the devices.", e);
            throw new NetshotBadRequestException("Unable to get the stats",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsLightSoftwareLevelDevice.
     */
    @XmlRootElement
    @XmlAccessorType(value = XmlAccessType.NONE)
    public static class RsLightAccessFailureDevice extends RsLightDevice {

        private Date lastSuccess;

        private Date lastFailure;

        @XmlElement
        public Date getLastSuccess() {
            return lastSuccess;
        }

        public void setLastSuccess(Date lastSuccess) {
            this.lastSuccess = lastSuccess;
        }

        @XmlElement
        public Date getLastFailure() {
            return lastFailure;
        }

        public void setLastFailure(Date lastFailure) {
            this.lastFailure = lastFailure;
        }
    }

    @GET
    @Path("reports/accessfailuredevices/{days}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<RsLightAccessFailureDevice> getAccessFailureDevices(@PathParam("days") Integer days)
            throws WebApplicationException {
        logger.debug("REST request, devices without successful snapshot over the last {} days.", days);

        if (days == null || days < 1) {
            logger.warn("Invalid number of days {} to find the unreachable devices, using 3.", days);
            days = 3;
        }

        Session session = Database.getSession();

        try {
            Calendar when = Calendar.getInstance();
            when.add(Calendar.DATE, -days);

            @SuppressWarnings("unchecked")
            List<RsLightAccessFailureDevice> devices = session.createQuery(DEVICELIST_BASEQUERY
                    + ", (select max(t.executionDate) from TakeSnapshotTask t where t.device = d and t.status = :success) as lastSuccess, (select max(t.executionDate) from TakeSnapshotTask t where t.device = d and t.status = :failure) as lastFailure from Device d where d.status = :enabled")
                    .setParameter("success", Task.Status.SUCCESS).setParameter("failure", Task.Status.FAILURE)
                    .setParameter("enabled", Device.Status.INPRODUCTION)
                    .setResultTransformer(Transformers.aliasToBean(RsLightAccessFailureDevice.class)).list();
            Iterator<RsLightAccessFailureDevice> d = devices.iterator();
            while (d.hasNext()) {
                RsLightAccessFailureDevice device = d.next();
                if (device.getLastSuccess() != null && device.getLastSuccess().after(when.getTime())) {
                    d.remove();
                }
            }
            return devices;
        } catch (HibernateException e) {
            logger.error("Unable to get the devices.", e);
            throw new NetshotBadRequestException("Unable to get the stats",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsLogin.
     */
    @XmlRootElement
    @XmlAccessorType(value = XmlAccessType.NONE)
    public static class RsLogin {

        /** The username. */
        private String username;

        /** The password. */
        private String password;

        /** The new password. */
        private String newPassword = "";

        /**
         * Gets the username.
         *
         * @return the username
         */
        @XmlElement
        public String getUsername() {
            return username;
        }

        /**
         * Sets the username.
         *
         * @param username the new username
         */
        public void setUsername(String username) {
            this.username = username;
        }

        /**
         * Gets the password.
         *
         * @return the password
         */
        @XmlElement
        public String getPassword() {
            return password;
        }

        /**
         * Sets the password.
         *
         * @param password the new password
         */
        public void setPassword(String password) {
            this.password = password;
        }

        /**
         * Gets the new password.
         *
         * @return the new password
         */
        @XmlElement
        public String getNewPassword() {
            return newPassword;
        }

        /**
         * Sets the new password.
         *
         * @param newPassword the new new password
         */
        public void setNewPassword(String newPassword) {
            this.newPassword = newPassword;
        }
    }

    /**
     * Logout.
     *
     * @param request the request
     * @return the boolean
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("user/{id}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void logout(@Context HttpServletRequest request) throws WebApplicationException {
        logger.debug("REST logout request.");
        HttpSession httpSession = request.getSession();
        httpSession.invalidate();
    }

    /**
     * Sets the password.
     *
     * @param request the request
     * @param rsLogin the rs login
     * @return the user
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("user/{id}")
    @RolesAllowed("readonly")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public User setPassword(@Context HttpServletRequest request, RsLogin rsLogin) throws WebApplicationException {
        logger.debug("REST password change request, username {}.", rsLogin.getUsername());
        User sessionUser = (User) request.getSession().getAttribute("user");

        User user;
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            user = (User) session.bySimpleNaturalId(User.class).load(rsLogin.getUsername());
            if (user == null || !user.getUsername().equals(sessionUser.getUsername()) || !user.isLocal()) {
                throw new NetshotBadRequestException("Invalid user.",
                        NetshotBadRequestException.NETSHOT_INVALID_USER);
            }

            if (!user.checkPassword(rsLogin.getPassword())) {
                throw new NetshotBadRequestException("Invalid current password.",
                        NetshotBadRequestException.NETSHOT_INVALID_USER);
            }

            String newPassword = rsLogin.getNewPassword();
            if (newPassword.equals("")) {
                throw new NetshotBadRequestException("The password cannot be empty.",
                        NetshotBadRequestException.NETSHOT_INVALID_USER);
            }

            user.setPassword(newPassword);
            session.save(user);
            session.getTransaction().commit();
            return sessionUser;
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to retrieve the user {}.", rsLogin.getUsername(), e);
            throw new NetshotBadRequestException("Unable to retrieve the user.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * Login.
     *
     * @param request the request
     * @param rsLogin the rs login
     * @return the user
     * @throws WebApplicationException the web application exception
     */
    @POST
    @PermitAll
    @Path("user")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public User login(@Context HttpServletRequest request, RsLogin rsLogin) throws WebApplicationException {
        logger.debug("REST authentication request, username {}.", rsLogin.getUsername());

        User user = null;

        Session session = Database.getSession();
        try {
            user = (User) session.bySimpleNaturalId(User.class).load(rsLogin.getUsername());
        } catch (HibernateException e) {
            logger.error("Unable to retrieve the user {}.", rsLogin.getUsername(), e);
            throw new NetshotBadRequestException("Unable to retrieve the user.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }

        if (user != null && user.isLocal()) {
            if (!user.checkPassword(rsLogin.getPassword())) {
                user = null;
            }
        } else {
            User remoteUser = Radius.authenticate(rsLogin.getUsername(), rsLogin.getPassword());
            if (remoteUser != null && user != null) {
                remoteUser.setLevel(user.getLevel());
            }
            user = remoteUser;
        }
        if (user == null) {
            HttpSession httpSession = request.getSession();
            httpSession.invalidate();
        } else {
            HttpSession httpSession = request.getSession();
            httpSession.setAttribute("user", user);
            httpSession.setMaxInactiveInterval(User.MAX_IDLE_TIME);
            return user;
        }
        throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED).build());
    }

    /**
     * Gets the user.
     *
     * @param request the request
     * @return the user
     * @throws WebApplicationException the web application exception
     */
    @GET
    @RolesAllowed("readonly")
    @Path("user")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public User getUser(@Context HttpServletRequest request) throws WebApplicationException {
        User user = (User) request.getSession().getAttribute("user");
        return user;
    }

    /**
     * Gets the users.
     *
     * @param request the request
     * @return the users
     * @throws WebApplicationException the web application exception
     */
    @GET
    @Path("/users")
    @RolesAllowed("admin")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<User> getUsers() throws WebApplicationException {
        logger.debug("REST request, get user list.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<User> users = session.createCriteria(User.class).list();
            return users;
        } catch (HibernateException e) {
            logger.error("Unable to retrieve the users.", e);
            throw new NetshotBadRequestException("Unable to retrieve the users.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    /**
     * The Class RsUser.
     */
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.NONE)
    public static class RsUser {

        /** The id. */
        private long id;

        /** The username. */
        private String username;

        /** The password. */
        private String password;

        /** The level. */
        private int level;

        /** The local. */
        private boolean local;

        /**
         * Gets the id.
         *
         * @return the id
         */
        @XmlElement
        public long getId() {
            return id;
        }

        /**
         * Sets the id.
         *
         * @param id the new id
         */
        public void setId(long id) {
            this.id = id;
        }

        /**
         * Gets the username.
         *
         * @return the username
         */
        @XmlElement
        public String getUsername() {
            return username;
        }

        /**
         * Sets the username.
         *
         * @param username the new username
         */
        public void setUsername(String username) {
            this.username = username;
        }

        /**
         * Gets the password.
         *
         * @return the password
         */
        @XmlElement
        public String getPassword() {
            return password;
        }

        /**
         * Sets the password.
         *
         * @param password the new password
         */
        public void setPassword(String password) {
            this.password = password;
        }

        /**
         * Gets the level.
         *
         * @return the level
         */
        @XmlElement
        public int getLevel() {
            return level;
        }

        /**
         * Sets the level.
         *
         * @param level the new level
         */
        public void setLevel(int level) {
            this.level = level;
        }

        /**
         * Checks if is local.
         *
         * @return true, if is local
         */
        @XmlElement
        public boolean isLocal() {
            return local;
        }

        /**
         * Sets the local.
         *
         * @param local the new local
         */
        public void setLocal(boolean local) {
            this.local = local;
        }
    }

    /**
     * Adds the user.
     *
     * @param request the request
     * @param rsUser the rs user
     * @return the user
     */
    @POST
    @Path("users")
    @RolesAllowed("admin")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public User addUser(RsUser rsUser) {
        logger.debug("REST request, add user");

        String username = rsUser.getUsername();
        if (username == null || username.trim().isEmpty()) {
            logger.warn("User posted an empty user name.");
            throw new NetshotBadRequestException("Invalid user name.",
                    NetshotBadRequestException.NETSHOT_INVALID_USER_NAME);
        }
        username = username.trim();

        String password = rsUser.getPassword();
        if (rsUser.isLocal()) {
            if (password == null || password.equals("")) {
                logger.warn("User tries to create a local account without password.");
                throw new NetshotBadRequestException("Please set a password.",
                        NetshotBadRequestException.NETSHOT_INVALID_PASSWORD);
            }
        } else {
            password = "";
        }

        User user = new User(username, rsUser.isLocal(), password);
        user.setLevel(rsUser.level);

        Session session = Database.getSession();
        try {
            session.beginTransaction();
            session.save(user);
            session.getTransaction().commit();
            return user;
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the new user.", e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A user with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_USER);
            }
            throw new NetshotBadRequestException("Unable to add the group to the database",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }

    }

    /**
     * Sets the user.
     *
     * @param request the request
     * @param id the id
     * @param rsUser the rs user
     * @return the user
     * @throws WebApplicationException the web application exception
     */
    @PUT
    @Path("users/{id}")
    @RolesAllowed("admin")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public User setUser(@PathParam("id") Long id, RsUser rsUser) throws WebApplicationException {
        logger.debug("REST request, edit user {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            User user = (User) session.get(User.class, id);
            if (user == null) {
                logger.error("Unable to find the user {} to be edited.", id);
                throw new NetshotBadRequestException("Unable to find this user.",
                        NetshotBadRequestException.NETSHOT_INVALID_USER);
            }

            String username = rsUser.getUsername();
            if (username == null || username.trim().isEmpty()) {
                logger.warn("User posted an empty user name.");
                throw new NetshotBadRequestException("Invalid user name.",
                        NetshotBadRequestException.NETSHOT_INVALID_USER_NAME);
            }
            username = username.trim();
            user.setUsername(username);

            user.setLevel(rsUser.getLevel());
            if (rsUser.isLocal()) {
                if (rsUser.getPassword() != null && !rsUser.getPassword().equals("-")) {
                    user.setPassword(rsUser.getPassword());
                }
                if (user.getHashedPassword().equals("")) {
                    logger.error("The password cannot be empty for user {}.", id);
                    throw new NetshotBadRequestException("You must set a password.",
                            NetshotBadRequestException.NETSHOT_INVALID_PASSWORD);
                }
            } else {
                user.setPassword("");
            }
            user.setLocal(rsUser.isLocal());
            session.update(user);
            session.getTransaction().commit();
            return user;
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to save the user {}.", id, e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A user with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_USER);
            }
            throw new NetshotBadRequestException("Unable to save the user.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } catch (WebApplicationException e) {
            session.getTransaction().rollback();
            throw e;
        } finally {
            session.close();
        }
    }

    /**
     * Delete user.
     *
     * @param request the request
     * @param id the id
     * @throws WebApplicationException the web application exception
     */
    @DELETE
    @Path("users/{id}")
    @RolesAllowed("admin")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deleteUser(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete user {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            User user = (User) session.load(User.class, id);
            session.delete(user);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The user doesn't exist.");
            throw new NetshotBadRequestException("The user doesn't exist.",
                    NetshotBadRequestException.NETSHOT_INVALID_USER);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            throw new NetshotBadRequestException("Unable to delete the user.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    @GET
    @Path("reports/export")
    @RolesAllowed("readonly")
    @Produces({ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" })
    public Response getDataXLSX(@Context HttpServletRequest request,
            @DefaultValue("-1") @QueryParam("group") long group,
            @DefaultValue("false") @QueryParam("interfaces") boolean exportInterfaces,
            @DefaultValue("false") @QueryParam("inventory") boolean exportInventory,
            @DefaultValue("xlsx") @QueryParam("format") String fileFormat) throws WebApplicationException {
        logger.debug("REST request, export data.");
        User user = (User) request.getSession().getAttribute("user");

        if (fileFormat.compareToIgnoreCase("xlsx") == 0) {
            String fileName = String.format("netshot-export_%s.xlsx",
                    (new SimpleDateFormat("yyyyMMdd-HHmmss")).format(new Date()));

            Session session = Database.getSession();
            try {
                Workbook workBook = new XSSFWorkbook();
                Row row;
                Cell cell;

                CreationHelper createHelper = workBook.getCreationHelper();
                CellStyle datetimeCellStyle = workBook.createCellStyle();
                datetimeCellStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd hh:mm"));
                CellStyle dateCellStyle = workBook.createCellStyle();
                dateCellStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));

                Sheet summarySheet = workBook.createSheet("Summary");
                row = summarySheet.createRow(0);
                row.createCell(0).setCellValue("Netshot version");
                row.createCell(1).setCellValue(Netshot.VERSION);
                row = summarySheet.createRow(1);
                row.createCell(0).setCellValue("Exported by");
                row.createCell(1).setCellValue(user.getName());
                row = summarySheet.createRow(2);
                row.createCell(0).setCellValue("Date and time");
                cell = row.createCell(1);
                cell.setCellValue(new Date());
                cell.setCellStyle(datetimeCellStyle);
                row = summarySheet.createRow(4);
                row.createCell(0).setCellValue("Selected Group");
                Query query;
                if (group == -1) {
                    query = session.createQuery("select d from Device d");
                    row.createCell(1).setCellValue("None");
                } else {
                    query = session.createQuery("select d from Device d join d.ownerGroups g where g.id = :id")
                            .setLong("id", group);
                    DeviceGroup deviceGroup = (DeviceGroup) session.get(DeviceGroup.class, group);
                    row.createCell(1).setCellValue(deviceGroup.getName());
                }

                Sheet deviceSheet = workBook.createSheet("Devices");
                row = deviceSheet.createRow(0);
                row.createCell(0).setCellValue("ID");
                row.createCell(1).setCellValue("Name");
                row.createCell(2).setCellValue("Management IP");
                row.createCell(3).setCellValue("Domain");
                row.createCell(4).setCellValue("Network Class");
                row.createCell(5).setCellValue("Family");
                row.createCell(6).setCellValue("Creation");
                row.createCell(7).setCellValue("Last Change");
                row.createCell(8).setCellValue("Software");
                row.createCell(9).setCellValue("End of Sale Date");
                row.createCell(10).setCellValue("End Of Life Date");

                int yDevice = 1;

                @SuppressWarnings("unchecked")
                List<Device> devices = query.list();
                for (Device device : devices) {
                    row = deviceSheet.createRow(yDevice++);
                    row.createCell(0).setCellValue(device.getId());
                    row.createCell(1).setCellValue(device.getName());
                    row.createCell(2).setCellValue(device.getMgmtAddress().getIp());
                    row.createCell(3).setCellValue(device.getMgmtDomain().getName());
                    row.createCell(4).setCellValue(device.getNetworkClass().toString());
                    row.createCell(5).setCellValue(device.getFamily());
                    cell = row.createCell(6);
                    cell.setCellValue(device.getCreatedDate());
                    cell.setCellStyle(datetimeCellStyle);
                    cell = row.createCell(7);
                    cell.setCellValue(device.getChangeDate());
                    cell.setCellStyle(datetimeCellStyle);
                    row.createCell(8).setCellValue(device.getSoftwareVersion());
                    if (device.getEosDate() != null) {
                        cell = row.createCell(9);
                        cell.setCellValue(device.getEosDate());
                        cell.setCellStyle(dateCellStyle);
                    }
                    if (device.getEolDate() != null) {
                        cell = row.createCell(10);
                        cell.setCellValue(device.getEolDate());
                        cell.setCellStyle(dateCellStyle);
                    }
                }

                if (exportInterfaces) {
                    Sheet interfaceSheet = workBook.createSheet("Interfaces");
                    row = interfaceSheet.createRow(0);
                    row.createCell(0).setCellValue("Device ID");
                    row.createCell(1).setCellValue("Virtual Device");
                    row.createCell(2).setCellValue("Name");
                    row.createCell(3).setCellValue("Description");
                    row.createCell(4).setCellValue("VRF");
                    row.createCell(5).setCellValue("MAC Address");
                    row.createCell(6).setCellValue("Enabled");
                    row.createCell(7).setCellValue("Level 3");
                    row.createCell(8).setCellValue("IP Address");
                    row.createCell(9).setCellValue("Mask Length");
                    row.createCell(10).setCellValue("Usage");

                    int yInterface = 1;
                    for (Device device : devices) {
                        for (NetworkInterface networkInterface : device.getNetworkInterfaces()) {
                            if (networkInterface.getIpAddresses().size() == 0) {
                                row = interfaceSheet.createRow(yInterface++);
                                row.createCell(0).setCellValue(device.getId());
                                row.createCell(1).setCellValue(networkInterface.getVirtualDevice());
                                row.createCell(2).setCellValue(networkInterface.getInterfaceName());
                                row.createCell(3).setCellValue(networkInterface.getDescription());
                                row.createCell(4).setCellValue(networkInterface.getVrfInstance());
                                row.createCell(5).setCellValue(networkInterface.getMacAddress());
                                row.createCell(6).setCellValue(networkInterface.isEnabled());
                                row.createCell(7).setCellValue(networkInterface.isLevel3());
                                row.createCell(8).setCellValue("");
                                row.createCell(9).setCellValue("");
                                row.createCell(10).setCellValue("");
                            }
                            for (NetworkAddress address : networkInterface.getIpAddresses()) {
                                row = interfaceSheet.createRow(yInterface++);
                                row.createCell(0).setCellValue(device.getId());
                                row.createCell(1).setCellValue(networkInterface.getVirtualDevice());
                                row.createCell(2).setCellValue(networkInterface.getInterfaceName());
                                row.createCell(3).setCellValue(networkInterface.getDescription());
                                row.createCell(4).setCellValue(networkInterface.getVrfInstance());
                                row.createCell(5).setCellValue(networkInterface.getMacAddress());
                                row.createCell(6).setCellValue(networkInterface.isEnabled());
                                row.createCell(7).setCellValue(networkInterface.isLevel3());
                                row.createCell(8).setCellValue(address.getIp());
                                row.createCell(9).setCellValue(address.getPrefixLength());
                                row.createCell(10).setCellValue(address.getAddressUsage() == null ? ""
                                        : address.getAddressUsage().toString());
                            }
                        }
                    }
                }

                if (exportInventory) {
                    Sheet inventorySheet = workBook.createSheet("Inventory");
                    row = inventorySheet.createRow(0);
                    row.createCell(0).setCellValue("Device ID");
                    row.createCell(1).setCellValue("Slot");
                    row.createCell(2).setCellValue("Part Number");
                    row.createCell(3).setCellValue("Serial Number");

                    int yInventory = 1;
                    for (Device device : devices) {
                        for (Module module : device.getModules()) {
                            row = inventorySheet.createRow(yInventory++);
                            row.createCell(0).setCellValue(device.getId());
                            row.createCell(1).setCellValue(module.getSlot());
                            row.createCell(2).setCellValue(module.getPartNumber());
                            row.createCell(3).setCellValue(module.getSerialNumber());
                        }
                    }
                }

                ByteArrayOutputStream output = new ByteArrayOutputStream();
                workBook.write(output);
                workBook.close();
                return Response.ok(output.toByteArray())
                        .header("Content-Disposition", "attachment; filename=" + fileName).build();
            } catch (IOException e) {
                logger.error("Unable to write the resulting file.", e);
                throw new WebApplicationException("Unable to write the resulting file.",
                        javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
            } catch (Exception e) {
                logger.error("Unable to generate the report.", e);
                throw new WebApplicationException("Unable to generate the report.",
                        javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR);
            } finally {
                session.close();
            }
        }

        logger.warn("Invalid requested file format.");
        throw new WebApplicationException("The requested file format is invalid or not supported.",
                javax.ws.rs.core.Response.Status.BAD_REQUEST);

    }

    @POST
    @Path("scripts")
    @RolesAllowed("readwrite")
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public DeviceJsScript addScript(@Context HttpServletRequest request, DeviceJsScript rsScript)
            throws WebApplicationException {
        logger.debug("REST request, add device script.");
        DeviceDriver driver = DeviceDriver.getDriverByName(rsScript.getDeviceDriver());
        if (driver == null) {
            logger.warn("Invalid driver name.");
            throw new NetshotBadRequestException("Invalid driver name.",
                    NetshotBadRequestException.NETSHOT_INVALID_SCRIPT);
        }
        if (rsScript.getName() == null || rsScript.getName().trim().equals("")) {
            logger.warn("Invalid script name.");
            throw new NetshotBadRequestException("Invalid script name.",
                    NetshotBadRequestException.NETSHOT_INVALID_SCRIPT);
        }
        if (rsScript.getScript() == null) {
            logger.warn("Invalid script.");
            throw new NetshotBadRequestException("The script content can't be empty.",
                    NetshotBadRequestException.NETSHOT_INVALID_SCRIPT);
        }
        try {
            User user = (User) request.getSession().getAttribute("user");
            rsScript.setAuthor(user.getUsername());
        } catch (Exception e) {
        }
        rsScript.setId(0);

        Session session = Database.getSession();
        try {
            session.beginTransaction();
            session.save(rsScript);
            session.getTransaction().commit();
            return rsScript;
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Error while saving the new rule.", e);
            Throwable t = e.getCause();
            if (t != null && t.getMessage().contains("Duplicate entry")) {
                throw new NetshotBadRequestException("A script with this name already exists.",
                        NetshotBadRequestException.NETSHOT_DUPLICATE_SCRIPT);
            }
            throw new NetshotBadRequestException("Unable to add the script to the database",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    @DELETE
    @Path("scripts/{id}")
    @RolesAllowed("readwrite")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public void deleteScript(@PathParam("id") Long id) throws WebApplicationException {
        logger.debug("REST request, delete script {}.", id);
        Session session = Database.getSession();
        try {
            session.beginTransaction();
            DeviceJsScript script = (DeviceJsScript) session.load(DeviceJsScript.class, id);
            session.delete(script);
            session.getTransaction().commit();
        } catch (ObjectNotFoundException e) {
            session.getTransaction().rollback();
            logger.error("The script {} to be deleted doesn't exist.", id, e);
            throw new NetshotBadRequestException("The script doesn't exist.",
                    NetshotBadRequestException.NETSHOT_INVALID_SCRIPT);
        } catch (HibernateException e) {
            session.getTransaction().rollback();
            logger.error("Unable to delete the script {}.", id, e);
            throw new NetshotBadRequestException("Unable to delete the script.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    @GET
    @Path("scripts/{id}")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public DeviceJsScript getScript(@PathParam("id") Long id) {
        logger.debug("REST request, get script {}", id);
        Session session = Database.getSession();
        try {
            DeviceJsScript script = (DeviceJsScript) session.get(DeviceJsScript.class, id);
            return script;
        } catch (ObjectNotFoundException e) {
            logger.error("Unable to find the script {}.", id, e);
            throw new NetshotBadRequestException("Script not found.",
                    NetshotBadRequestException.NETSHOT_INVALID_SCRIPT);
        } catch (HibernateException e) {
            logger.error("Unable to fetch the script {}.", id, e);
            throw new NetshotBadRequestException("Unable to fetch the script.",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

    @GET
    @Path("scripts")
    @RolesAllowed("readonly")
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public List<DeviceJsScript> getScripts() {
        logger.debug("REST request, get scripts.");
        Session session = Database.getSession();
        try {
            @SuppressWarnings("unchecked")
            List<DeviceJsScript> scripts = session.createQuery("from DeviceJsScript s").list();
            for (DeviceJsScript script : scripts) {
                script.setScript(null);
            }
            return scripts;
        } catch (HibernateException e) {
            logger.error("Unable to fetch the scripts.", e);
            throw new NetshotBadRequestException("Unable to fetch the scripts",
                    NetshotBadRequestException.NETSHOT_DATABASE_ACCESS_ERROR);
        } finally {
            session.close();
        }
    }

}