/*
 * RHQ Management Platform
 * Copyright (C) 2005-2009 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package org.rhq.core.domain.plugin;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.PrePersist;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

/**
 * Base plugin implementation that agent and server plugin implementations extend.
 * 
 * @author John Mazzitelli
 */
@DiscriminatorColumn(name = "DEPLOYMENT")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Entity
@SequenceGenerator(name = "SEQ", sequenceName = "RHQ_PLUGIN_ID_SEQ")
@Table(name = Plugin.TABLE_NAME)
public class AbstractPlugin implements Serializable {
    private static final long serialVersionUID = 1L;

    public static final String TABLE_NAME = "RHQ_PLUGIN";

    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ")
    @Id
    private int id;

    @Column(name = "DEPLOYMENT", nullable = false, insertable = false, updatable = false)
    @Enumerated(EnumType.STRING)
    private PluginDeploymentType deployment;

    @Column(name = "NAME", nullable = false)
    private String name;

    @Column(name = "DISPLAY_NAME", nullable = false)
    private String displayName;

    @Column(name = "DESCRIPTION", nullable = true)
    private String description;

    @Column(name = "ENABLED", nullable = false)
    private boolean enabled = true;

    @Column(name = "STATUS", nullable = false)
    @Enumerated(EnumType.STRING)
    private PluginStatusType status = PluginStatusType.INSTALLED;

    @Column(name = "HELP", nullable = true)
    private String help;

    @Column(name = "VERSION", nullable = true)
    private String version;

    @Column(name = "AMPS_VERSION", nullable = true)
    private String ampsVersion;

    @Column(name = "PATH", nullable = false)
    private String path;

    @Column(name = "MD5", nullable = false)
    private String md5;

    @Column(name = "CTIME", nullable = false)
    private long ctime;

    @Column(name = "MTIME", nullable = false)
    private long mtime;

    @Column(name = "CONTENT", nullable = true)
    private byte[] content;

    public AbstractPlugin() {
    }

    /**
     * Constructor for {@link Plugin}.
     *
     * @param name the logical name of the plugin
     * @param path the actual filename of the plugin jar (see {@link #getPath()})
     */
    public AbstractPlugin(String name, String path) {
        this.name = name;
        this.path = path;
    }

    /**
     * Constructor for {@link Plugin}.
     * Note that this allows you to provide an MD5 without providing the plugin's
     * actual content. If you wish to persist this entity in the database, you should
     * either provide the {@link #setContent(byte[]) content} or update the entity
     * later by streaming the file content to the content column.
     *
     * @param name the logical name of the plugin
     * @param path the actual filename of the plugin jar (see {@link #getPath()})
     * @param md5  the MD5 hash string of the plugin jar contents
     */
    public AbstractPlugin(String name, String path, String md5) {
        this.name = name;
        this.path = path;
        this.md5 = md5;
    }

    /**
     * Constructor for {@link Plugin}.
     *
     * @param name the logical name of the plugin
     * @param path the actual filename of the plugin jar (see {@link #getPath()})
     * @param content the actual jar file contents (the MD5 hash string will be generated from this)
     */
    public AbstractPlugin(String name, String path, byte[] content) {
        this.name = name;
        this.path = path;
        this.content = content;
    }

    /**
     * Constructor that can build the full object except for the content byte array.
     * This is used mainly for the named queries that want to return a Plugin object
     * but does not eagerly load in the content array.
     */
    public AbstractPlugin(int id, String name, String path, String displayName, boolean enabled,
        PluginStatusType status, String description, String help, String md5, String version, String ampsVersion,
        PluginDeploymentType deployment, long ctime, long mtime) {
        this.id = id;
        this.name = name;
        this.path = path;
        this.displayName = displayName;
        this.enabled = enabled;
        this.status = status;
        this.description = description;
        this.help = help;
        this.md5 = md5;
        this.version = version;
        this.ampsVersion = ampsVersion;
        this.deployment = deployment;
        this.ctime = ctime;
        this.mtime = mtime;
    }

    public int getId() {
        return this.id;
    }

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

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * See the javadoc of {@link #getMtime()} for
     * information about this field and its relationship
     * with "mtime".
     * 
     * @return the time when this entity was persisted
     */
    public long getCtime() {
        return this.ctime;
    }

    public void setCtime(long ctime) {
        this.ctime = ctime;
    }

    /**
     * The "mtime" of the plugin has slightly different semantics
     * than  other "mtime" values found elsewhere. The "mtime"
     * will typically be the time that the content field was modified,
     * not necessarily the time when any field was modified. In other
     * words, look at "mtime" if you want to know when the actual
     * plugin content was last updated. Note that this "mtime" may in
     * fact be the last modified time of the plugin file from which
     * the content came from - this means mtime may actually be earlier
     * in time than "ctime" (in the case when the plugin jar file was
     * last touched prior to this entity being created).
     * 
     * Note that the "ctime" field semantics remains the same as always,
     * it is the time when this entity was originally created.
     * 
     * @return mtime of the content
     */
    public long getMtime() {
        return this.mtime;
    }

    /**
     * This entity does not automatically update the "mtime" when it
     * is updated via a PreUpdate annotation, therefore, the owner of
     * this entity needs to explicitly call this setter in order to
     * set the "mtime". You normally set this value to the last
     * modified time of the plugin jar that provided
     * this plugin entity's {@link #getContent() content}.
     * 
     * @param mtime
     */
    public void setMtime(long mtime) {
        this.mtime = mtime;
    }

    public String getDisplayName() {
        return displayName;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public PluginStatusType getStatus() {
        return status;
    }

    public void setStatus(PluginStatusType status) {
        this.status = status;
        if (this.status == PluginStatusType.DELETED) {
            this.enabled = false;
        }
    }

    public String getHelp() {
        return help;
    }

    public void setHelp(String help) {
        this.help = help;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getAmpsVersion() {
        return ampsVersion;
    }

    public void setAmpsVersion(String ampsVersion) {
        this.ampsVersion = ampsVersion;
    }

    public String getMd5() {
        return md5;
    }

    public void setMd5(String md5) {
        this.md5 = md5;
    }

    public String getMD5() {
        return getMd5();
    }

    public void setMD5(String md5) {
        setMd5(md5);
    }

    /**
     * Returns the actual name of the plugin jar. This is not the absolute path, in fact, it does not include any
     * directory paths. It is strictly the name of the plugin jar as found on the file system (aka the filename).
     *
     * @return plugin filename
     */
    public String getPath() {
        return this.path;
    }

    /**
     * Ensure that the path being set does not include any directory names. The plugin path is the filename. See
     * {@link #getPath()}.
     *
     * @param path the filename of the plugin, not including directory names
     */
    public void setPath(String path) {
        this.path = path;
    }

    /**
     * Indicates how the plugin gets deployed (e.g. running in the agent or in the server).
     * 
     * @return plugin deployment type
     */
    public PluginDeploymentType getDeployment() {
        return deployment;
    }

    public void setDeployment(PluginDeploymentType deployment) {
        this.deployment = deployment;
    }

    /**
     * Returns the actual content of the plugin file. Be careful calling this
     * in an entity context - the entire plugin file content will be loaded in
     * memory (which may trigger an OutOfMemoryError if the file is very large).
     * 
     * @return the content of the plugin file
     */
    public byte[] getContent() {
        return this.content;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }

    @PrePersist
    void onPersist() {
        this.ctime = System.currentTimeMillis();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if ((obj == null) || !(obj instanceof AbstractPlugin)) {
            return false;
        }

        AbstractPlugin that = (AbstractPlugin) obj;
        return name.equals(that.name);
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public String toString() {
        return "[id=" + id + ", name=" + name + ", md5=" + md5 + "]";
    }
}
