webSpoon源码编译

一、8.3源码下载编译运行

git clone -b webspoon-8.3 https://gitee.com/HQYSTUDIO/hiromu-hota.pentaho-kettle.git
git clone -b webspoon-8.3 https://gitee.com/HQYSTUDIO/hiromu-hota.pentaho-commons-xul.git
git clone -b webspoon-3.7.0 https://gitee.com/HQYSTUDIO/hiromu-hota.rap.git
git clone -b webspoon-8.3 https://gitee.com/HQYSTUDIO/hiromu-hota.apache-vfs-browser.git


# 每个项目搜索 http://nexus.pentaho.org/content/groups/omni 仓库改为
<repositories>
    <repository>
      <id>pentaho-public</id>
      <name>Pentaho Public</name>
      <url>https://nexus.pentaho.org/content/groups/omni/</url>
      <releases>
        <enabled>true</enabled>
        <updatePolicy>daily</updatePolicy>
      </releases>
      <snapshots>
        <enabled>true</enabled>
        <updatePolicy>interval:15</updatePolicy>
      </snapshots>
    </repository>
  </repositories>
  <pluginRepositories>
	<pluginRepository>
	  <id>pentaho-public</id>
	  <name>Pentaho Public</name>
	  <url>https://repo.orl.eng.hitachivantara.com/artifactory/pnt-mvn/</url>
	  <releases>
		<enabled>true</enabled>
		<updatePolicy>daily</updatePolicy>
	  </releases>
	  <snapshots>
		<enabled>true</enabled>
		<updatePolicy>interval:15</updatePolicy>
	  </snapshots>
	</pluginRepository>
  </pluginRepositories>


# 修改 rap/releng/org.eclipse.rap.build/pom.xml
http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.14.v20181113/
改为 
https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.48.v20220622/

http://build.eclipse.org/rt/rap/base-platforms/3.7/extra-dependencies/
改为
https://download.eclipse.org/rt/rap/base-platforms/3.14/extra-dependencies/


# 替换版本号
将webSpoon中的所有8.3.0.0-371-19替换为8.3.0.0-371
将pentaho-commons-xul中的8.3.0.0-371-20替换为8.3.0.0-371
将apache-vfs-browser中的<rap.version>3.11.0</rap.version>替换为<rap.version>3.7.0-SNAPSHOT</rap.version>
将apache-vfs-browser中的8.3.0.0-371-20替换为8.3.0.0-371


# 编译前可以增加下clickhouse插件
# public JobMeta loadJobMeta 添加一行 jobMeta.setRepository(repository);  
# 编译
mvn clean install -pl swt -DskipTests=true -f ./pentaho-commons-xul
mvn clean install -DskipTests=true -f ./rap
mvn clean install -DskipTests=true -f ./apache-vfs-browser
mvn clean install -DskipTests=true -f ./webSpoon


# 编译完成后拷贝解压运行
webSpoon\assemblies\pdi-server\target\pdi-server-8.3.0.0-371.tar.gz
tar zxvf pdi-server-8.3.0.0-371.tar.gz

# 开机自启
(systemctl stop webspoon||true) && (systemctl disable webspoon||true) && \
echo '
[Unit]
Description=webspoon
After=network.target
[Service]
Type=forking
WorkingDirectory=/opt/webspoon
Environment="JAVA_HOME=/opt/jdk1.8.0_181"
Environment="GPHOME_LOADERS=/usr/local/greenplum-db-clients"
ExecStart=/opt/webspoon/bin/startup.sh
ExecStop=/opt/webspoon/bin/shutdown.sh
ExecReload=/bin/kill -s HUP $MAINPID
PrivateTmp=True
[Install]
WantedBy=multi-user.target
' > /etc/systemd/system/webspoon.service && \
systemctl enable webspoon && (systemctl start webspoon||true) && \
systemctl status webspoon

# 查看实时日志
journalctl -fu webspoon -n100


# 启动文件
start-pentaho.sh
start-pentaho.bat

# 访问地址
http://localhost:8080/spoon/spoon

二、源码变更

 2.1 远程仓库会出现路径变空的问题

改下源码 webSpoon/engine/src/main/java/org/pentaho/di/repository/kdr/delegates/KettleDatabaseRepositoryJobDelegate.java -> Line: 319   public JobMeta loadJobMeta

添加一句:jobMeta.setRepository(repository);

  

2.2 增加数据库连接插件

修改 webSpoon\core\src\main\resources\kettle-database-types.xml

<database-type id="Clickhouse">
  <description>Clickhouse</description>
  <classname>org.pentaho.di.core.database.ClickhouseDatabaseMeta</classname>
</database-type>

修改 webSpoon\assemblies\lib\pom.xml

<dependency>
    <groupId>ru.yandex.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.2.6</version>
</dependency>

新增 webSpoon\core\src\main\java\org\pentaho\di\core\database\ClickhouseDatabaseMeta.java 

package org.pentaho.di.core.database;

import java.sql.ResultSet;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.exception.KettleDatabaseException;
import org.pentaho.di.core.plugins.DatabaseMetaPlugin;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.util.Utils;

/**
 * DatabaseMeta数据库插件-Clickhouse
 */
//@DatabaseMetaPlugin(type = "Clickhouse", typeDescription = "Clickhouse")
public class ClickhouseDatabaseMeta extends BaseDatabaseMeta implements DatabaseInterface {
    private static final String STRICT_BIGNUMBER_INTERPRETATION = "STRICT_NUMBER_38_INTERPRETATION";

    @Override
    public int[] getAccessTypeList() {
        return new int[] { DatabaseMeta.TYPE_ACCESS_NATIVE, DatabaseMeta.TYPE_ACCESS_JNDI };
    }

    @Override
    public int getDefaultDatabasePort() {
        return getAccessType() == DatabaseMeta.TYPE_ACCESS_NATIVE ? 8123 : -1;
    }

    /**
     * 当前数据库是否支持自增类型的字段
     */
    @Override
    public boolean supportsAutoInc() {
        return false;
    }

    /**
     * 获取限制读取条数的数据,追加再select语句后实现限制返回的结果数
     * @see DatabaseInterface#getLimitClause(int)
     */
    @Override
    public String getLimitClause(int nrRows) {
        return " WHERE ROWNUM <= " + nrRows;
    }

    /**
     * 返回获取表所有字段信息的语句
     * @param tableName
     * @return The SQL to launch.
     */
    @Override
    public String getSQLQueryFields(String tableName) {
        return "SELECT * FROM " + tableName + " WHERE 1=0";
    }

    @Override
    public String getSQLTableExists(String tablename) {
        return getSQLQueryFields(tablename);
    }

    @Override
    public String getSQLColumnExists(String columnname, String tablename) {
        return getSQLQueryColumnFields(columnname, tablename);
    }

    public String getSQLQueryColumnFields(String columnname, String tableName) {
        return "SELECT " + columnname + " FROM " + tableName + " WHERE 1=0";
    }

    @Override
    public boolean needsToLockAllTables() {
        return false;
    }

    @Override
    public String getDriverClass() {
        return this.getAccessType() == DatabaseMeta.TYPE_ACCESS_ODBC ? "sun.jdbc.odbc.JdbcOdbcDriver" : "ru.yandex.clickhouse.ClickHouseDriver";
    }

    @Override
    public String getURL(String hostname, String port, String databaseName) throws KettleDatabaseException {
        if (this.getAccessType() == DatabaseMeta.TYPE_ACCESS_ODBC) {
            return "jdbc:odbc:" + databaseName;
        } else if (this.getAccessType() == DatabaseMeta.TYPE_ACCESS_NATIVE) {
            String _hostname = hostname;
            String _port = port;
            String _databaseName = databaseName;
            String _SocketTimeOut = "?socket_timeout=600000";
            if (Utils.isEmpty(hostname)) {
                _hostname = "localhost";
            }

            if (Utils.isEmpty(port) || port.equals("-1")) {
                _port = "";
            }

            if (Utils.isEmpty(databaseName)) {
                throw new KettleDatabaseException("必须指定数据库名称");
            } else {
                if (!databaseName.startsWith("/")) {
                    _databaseName = "/" + databaseName;
                }

                return "jdbc:clickhouse://" + _hostname + (Utils.isEmpty(_port) ? "" : ":" + _port) + _databaseName + _SocketTimeOut;
            }
        } else {
            throw new KettleDatabaseException("不支持的数据库连接方式[" + this.getAccessType() + "]");
        }
    }

    /**
     * Oracle doesn't support options in the URL, we need to put these in a
     * Properties object at connection time...
     */
    @Override
    public boolean supportsOptionsInURL() {
        return false;
    }

    /**
     * @return true if the database supports sequences
     */
    @Override
    public boolean supportsSequences() {
        return true;
    }

    /**
     * Check if a sequence exists.
     *
     * @param sequenceName
     *            The sequence to check
     * @return The SQL to get the name of the sequence back from the databases data
     *         dictionary
     */
    @Override
    public String getSQLSequenceExists(String sequenceName) {
        int dotPos = sequenceName.indexOf('.');
        String sql = "";
        if (dotPos == -1) {
            // if schema is not specified try to get sequence which belongs to current user
            sql = "SELECT * FROM USER_SEQUENCES WHERE SEQUENCE_NAME = '" + sequenceName.toUpperCase() + "'";
        } else {
            String schemaName = sequenceName.substring(0, dotPos);
            String seqName = sequenceName.substring(dotPos + 1);
            sql = "SELECT * FROM ALL_SEQUENCES WHERE SEQUENCE_NAME = '" + seqName.toUpperCase()
                    + "' AND SEQUENCE_OWNER = '" + schemaName.toUpperCase() + "'";
        }

        return sql;
    }

    /**
     * Get the current value of a database sequence
     *
     * @param sequenceName
     *            The sequence to check
     * @return The current value of a database sequence
     */
    @Override
    public String getSQLCurrentSequenceValue(String sequenceName) {
        return "SELECT " + sequenceName + ".currval FROM DUAL";
    }

    /**
     * Get the SQL to get the next value of a sequence. (Oracle only)
     *
     * @param sequenceName
     *            The sequence name
     * @return the SQL to get the next value of a sequence. (Oracle only)
     */
    @Override
    public String getSQLNextSequenceValue(String sequenceName) {
        return "SELECT " + sequenceName + ".nextval FROM dual";
    }

    @Override
    public boolean supportsSequenceNoMaxValueOption() {
        return true;
    }

    /**
     * @return true if we need to supply the schema-name to getTables in order to
     *         get a correct list of items.
     */
    @Override
    public boolean useSchemaNameForTableList() {
        return true;
    }

    /**
     * @return true if the database supports synonyms
     */
    @Override
    public boolean supportsSynonyms() {
        return true;
    }

    /**
     * Generates the SQL statement to add a column to the specified table
     *
     * @param tablename
     *            The table to add
     * @param v
     *            The column defined as a value
     * @param tk
     *            the name of the technical key field
     * @param use_autoinc
     *            whether or not this field uses auto increment
     * @param pk
     *            the name of the primary key field
     * @param semicolon
     *            whether or not to add a semi-colon behind the statement.
     * @return the SQL statement to add a column to the specified table
     */
    @Override
    public String getAddColumnStatement(String tablename, ValueMetaInterface v, String tk, boolean use_autoinc, String pk, boolean semicolon) {
        return "ALTER TABLE " + tablename + " ADD " + getFieldDefinition(v, tk, pk, use_autoinc, true, false);
    }

    /**
     * Generates the SQL statement to drop a column from the specified table
     *
     * @param tablename
     *            The table to add
     * @param v
     *            The column defined as a value
     * @param tk
     *            the name of the technical key field
     * @param use_autoinc
     *            whether or not this field uses auto increment
     * @param pk
     *            the name of the primary key field
     * @param semicolon
     *            whether or not to add a semi-colon behind the statement.
     * @return the SQL statement to drop a column from the specified table
     */
    @Override
    public String getDropColumnStatement(String tablename, ValueMetaInterface v, String tk, boolean use_autoinc, String pk, boolean semicolon) {
        return "ALTER TABLE " + tablename + " DROP COLUMN " + v.getName() + Const.CR;
    }

    /**
     * Generates the SQL statement to modify a column in the specified table
     *
     * @param tablename
     *            The table to add
     * @param v
     *            The column defined as a value
     * @param tk
     *            the name of the technical key field
     * @param use_autoinc
     *            whether or not this field uses auto increment
     * @param pk
     *            the name of the primary key field
     * @param semicolon
     *            whether or not to add a semi-colon behind the statement.
     * @return the SQL statement to modify a column in the specified table
     */
    @Override
    public String getModifyColumnStatement(String tablename, ValueMetaInterface v, String tk, boolean use_autoinc, String pk, boolean semicolon) {
        ValueMetaInterface tmpColumn = v.clone();
        String tmpName = v.getName();
        boolean isQuoted = tmpName.startsWith("\"") && tmpName.endsWith("\"");
        if (isQuoted) {
            // remove the quotes first.
            tmpName = tmpName.substring(1, tmpName.length() - 1);
        }

        int threeoh = tmpName.length() >= 30 ? 30 : tmpName.length();
        tmpName = tmpName.substring(0, threeoh);

        tmpName = tmpName + "_KTL"; // should always be shorter than 35 positions

        // put the quotes back if needed.
        if (isQuoted) {
            tmpName = "\"" + tmpName + "\"";
        }

        tmpColumn.setName(tmpName);

        // Read to go.
        String sql = "";

        // Create a new tmp column
        sql = sql + this.getAddColumnStatement(tablename, tmpColumn, tk, use_autoinc, pk, semicolon) + ";" + Const.CR;
        // copy the old data over to the tmp column
        sql = sql + "UPDATE " + tablename + " SET " + tmpColumn.getName() + "=" + v.getName() + ";" + Const.CR;
        // drop the old column
        sql = sql + this.getDropColumnStatement(tablename, v, tk, use_autoinc, pk, semicolon) + ";" + Const.CR;
        // create the wanted column
        sql = sql + this.getAddColumnStatement(tablename, v, tk, use_autoinc, pk, semicolon) + ";" + Const.CR;
        // copy the data from the tmp column to the wanted column (again)
        // All this to avoid the rename clause as this is not supported on all Oracle
        // versions
        sql = sql + "UPDATE " + tablename + " SET " + v.getName() + "=" + tmpColumn.getName() + ";" + Const.CR;
        // drop the temp column
        sql = sql + this.getDropColumnStatement(tablename, tmpColumn, tk, use_autoinc, pk, semicolon);

        return sql;
    }

    @Override
    public String getFieldDefinition(ValueMetaInterface v, String tk, String pk, boolean use_autoinc, boolean add_fieldname, boolean add_cr) {
        StringBuilder retval = new StringBuilder(128);

        String fieldname = v.getName();
        int length = v.getLength();
        int precision = v.getPrecision();

        if (add_fieldname) {
            retval.append(fieldname).append(" ");
        }

        int type = v.getType();
        switch(type) {
            case ValueMetaInterface.TYPE_NUMBER:
            case ValueMetaInterface.TYPE_INTEGER:
            case ValueMetaInterface.TYPE_BIGNUMBER:
                if (!fieldname.equalsIgnoreCase(tk) // Technical key
                        && !fieldname.equalsIgnoreCase(pk) // Primary key
                ) {
                    if (length > 0) {
                        if (precision <= 0 && length <= 18) {
                            if (precision == 0) {
                                if (length > 9) {
                                    retval.append("BIGINT");
                                } else if (length < 5) {
                                    retval.append("SMALLINT");
                                } else {
                                    retval.append("INT");
                                }
                            } else {
                                retval.append("FLOAT(53)");
                            }
                        } else {
                            // Numeric(Precision, Scale): Precision = total length; Scale = decimal places
                            retval.append("NUMERIC(").append(length + precision).append(", ").append(precision).append(")");
                        }
                    } else {
                        retval.append("DOUBLE PRECISION");
                    }
                } else {
                    retval.append("BIGSERIAL");
                }
                break;
            case ValueMetaInterface.TYPE_STRING:
                if (length >= 1 && length < DatabaseMeta.CLOB_LENGTH) {
                    retval.append("VARCHAR(").append(length).append(")");
                } else {
                    retval.append("TEXT");
                }
                break;
            case ValueMetaInterface.TYPE_DATE:
            case ValueMetaInterface.TYPE_TIMESTAMP:
                retval.append("TIMESTAMP");
                break;
            case ValueMetaInterface.TYPE_BOOLEAN:
                if (supportsBooleanDataType()) {
                    retval.append("BOOLEAN");
                } else {
                    retval.append("CHAR(1)");
                }
                break;
            case ValueMetaInterface.TYPE_BINARY:
                retval.append("BLOB");
                break;
            case ValueMetaInterface.TYPE_SERIALIZABLE:
            default:
                retval.append(" UNKNOWN");
                break;
        }

        if (add_cr) {
            retval.append(Const.CR);
        }

        return retval.toString();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.ibridge.kettle.core.database.DatabaseInterface#getReservedWords()
     */
    @Override
    public String[] getReservedWords() {
        return new String[]{"ALIAS", "AND", "AS", "AT", "BEGIN", "BETWEEN", "BIGINT", "BIT", "BY", "BOOLEAN", "BOTH", "CALL", "CASE", "CAST", "CHAR", "CHARACTER", "COMMIT", "CONSTANT", "CURSOR", "COALESCE", "CONTINUE", "CONVERT", "CURRENT_DATE", "CURRENT_TIMESTAMP", "CURRENT_USER", "DATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DECODE", "DELETE", "ELSE", "ELSIF", "END", "EXCEPTION", "EXECUTE", "EXIT", "EXTRACT", "FALSE", "FETCH", "FLOAT", "FOR", "FROM", "FUNCTION", "GOTO", "IF", "IN", "INT", "INTO", "IS", "INTEGER", "IMMEDIATE", "INDEX", "INOUT", "INSERT", "LEADING", "LIKE", "LIMIT", "LOCALTIME", "LOCALTIMESTAMP", "LOOP", "NCHAR", "NEXT", "NOCOPY", "NOT", "NULLIF", "NULL", "NUMBER", "NUMERIC", "OPTION", "OF", "OR", "OUT", "OVERLAY", "PERFORM", "POSITION", "PRAGMA", "PROCEDURE", "QUERY", "RAISE", "RECORD", "RENAME", "RETURN", "REVERSE", "ROLLBACK", "REAL", "SELECT", "SAVEPOINT", "SETOF", "SMALLINT", "SUBSTRING", "SQL", "SYSDATE", "SESSION_USER", "THEN", "TO", "TYPE", "TABLE", "TIME", "TIMESTAMP", "TINYINT", "TRAILING", "TREAT", "TRIM", "TRUE", "TYPE", "UID", "UPDATE", "USER", "USING", "VARCHAR", "VARCHAR2", "VALUES", "WITH", "WHEN", "WHILE", "LEVEL"};
    }

    /**
     * @return The SQL on this database to get a list of stored procedures.
     */
    @Override
    public String getSQLListOfProcedures() {
        /*
         * return
         * "SELECT DISTINCT DECODE(package_name, NULL, '', package_name||'.') || object_name "
         * + "FROM user_arguments " + "ORDER BY 1";
         */
        return "show tables";
    }

    @Override
    public String getSQLLockTables(String[] tableNames) {
        StringBuilder sql = new StringBuilder(128);

        for(int i = 0; i < tableNames.length; ++i) {
            sql.append("LOCK TABLE ").append(tableNames[i]).append(" IN EXCLUSIVE MODE;").append(Const.CR);
        }

        return sql.toString();
    }

    @Override
    public String getSQLUnlockTables(String[] tableNames) {
        return null; // commit handles the unlocking!
    }

    /**
     * @return extra help text on the supported options on the selected database
     *         platform.
     */
    @Override
    public String getExtraOptionsHelpText() {
        return "http://www.shentongdata.com/?bid=3&eid=249";
    }

    @Override
    public String[] getUsedLibraries() {
        return new String[]{"clickhouseJDBC.jar", "clickhouseJDBC14.jar", "clickhouseJDBC16.jar"};
    }

    /**
     * Verifies on the specified database connection if an index exists on the
     * fields with the specified name.
     *
     * @param database
     *            a connected database
     * @param schemaName
     * @param tableName
     * @param idx_fields
     * @return true if the index exists, false if it doesn't.
     * @throws KettleDatabaseException
     */
    @Override
    public boolean checkIndexExists(Database database, String schemaName, String tableName, String[] idx_fields) throws KettleDatabaseException {
        String tablename = database.getDatabaseMeta().getQuotedSchemaTableCombination(schemaName, tableName);
        boolean[] exists = new boolean[idx_fields.length];

        for(int i = 0; i < exists.length; ++i) {
            exists[i] = false;
        }

        try {
            String sql = "SELECT * FROM USER_IND_COLUMNS WHERE TABLE_NAME = '" + tableName + "'";
            ResultSet res = null;

            boolean all;
            try {
                res = database.openQuery(sql);
                if (res == null) {
                    all = false;
                    return all;
                }

                for(Object[] row = database.getRow(res); row != null; row = database.getRow(res)) {
                    String column = database.getReturnRowMeta().getString(row, "COLUMN_NAME", "");
                    int idx = Const.indexOfString(column, idx_fields);
                    if (idx >= 0) {
                        exists[idx] = true;
                    }
                }
            } finally {
                if (res != null) {
                    database.closeQuery(res);
                }

            }

            // See if all the fields are indexed...
            all = true;
            for(int i = 0; i < exists.length && all; ++i) {
                if (!exists[i]) {
                    all = false;
                }
            }

            return all;
        } catch (Exception var16) {
            throw new KettleDatabaseException("Unable to determine if indexes exists on table [" + tablename + "]", var16);
        }
    }

    @Override
    public boolean requiresCreateTablePrimaryKeyAppend() {
        return true;
    }

    /**
     * Most databases allow you to retrieve result metadata by preparing a SELECT
     * statement.
     *
     * @return true if the database supports retrieval of query metadata from a
     *         prepared statement. False if the query needs to be executed first.
     */
    @Override
    public boolean supportsPreparedStatementMetadataRetrieval() {
        return false;
    }

    /**
     * @return The maximum number of columns in a database, <=0 means: no known
     *         limit
     */
    @Override
    public int getMaxColumnsInIndex() {
        return 32;
    }

    /**
     * @return The SQL on this database to get a list of sequences.
     */
    @Override
    public String getSQLListOfSequences() {
        return "SELECT SEQUENCE_NAME FROM all_sequences";
    }

    /**
     * @param string
     * @return A string that is properly quoted for use in an Oracle SQL statement
     *         (insert, update, delete, etc)
     */
    @Override
    public String quoteSQLString(String string) {
        string = string.replaceAll("'", "''");
        string = string.replaceAll("\\n", "'||chr(13)||'");
        string = string.replaceAll("\\r", "'||chr(10)||'");
        return "'" + string + "'";
    }

    /**
     * Returns a false as Oracle does not allow for the releasing of savepoints.
     */
    @Override
    public boolean releaseSavepoint() {
        return false;
    }

    @Override
    public boolean supportsErrorHandlingOnBatchUpdates() {
        return false;
    }

    /**
     * @return true if Kettle can create a repository on this type of database.
     */
    @Override
    public boolean supportsRepository() {
        return true;
    }

    @Override
    public int getMaxVARCHARLength() {
        return 2000;
    }

    /**
     * Oracle does not support a construct like 'drop table if exists', which is
     * apparently legal syntax in many other RDBMSs. So we need to implement the
     * same behavior and avoid throwing 'table does not exist' exception.
     *
     * @param tableName
     *            Name of the table to drop
     * @return 'drop table if exists'-like statement for Oracle
     */
    @Override
    public String getDropTableIfExistsStatement(String tableName) {
        return "DROP TABLE IF EXISTS " + tableName;
    }

    @Override
    public SqlScriptParser createSqlScriptParser() {
        return new SqlScriptParser(false);
    }

    /**
     * @return true if using strict number(38) interpretation
     */
    public boolean strictBigNumberInterpretation() {
        return "Y".equalsIgnoreCase(this.getAttributes().getProperty("STRICT_NUMBER_38_INTERPRETATION", "N"));
    }

    /**
     * @param strictBigNumberInterpretation
     *            true if use strict number(38) interpretation
     */
    public void setStrictBigNumberInterpretation(boolean strictBigNumberInterpretation) {
        this.getAttributes().setProperty("STRICT_NUMBER_38_INTERPRETATION", strictBigNumberInterpretation ? "Y" : "N");
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
WebSpoon是一个基于Web的数据分析和可视化工具,它建立在Pentaho中间件之上,提供了一个可视化界面来管理和执行数据分析任务。要运行WebSpoon源代码,你需要按照以下步骤进行操作: 1. 下载WebSpoon源码:你可以从WebSpoon的官方GitHub仓库中下载源代码。确保下载最新的版本。 2. 安装Java开发工具包(Java Development Kit,JDK):WebSpoon是用Java编写的,所以你需要安装最新的JDK。可以从Oracle官方网站上下载并安装适合你操作系统的JDK版本。 3. 安装Maven:Maven是一个软件项目管理和构建工具。你可以从Apache Maven官方网站上下载并安装Maven。 4. 在命令行中导航到WebSpoon源码的根目录。 5. 执行Maven命令:运行命令 "mvn clean install" 来构建WebSpoon项目并下载所需的依赖项。这个过程可能会花费一些时间,具体取决于你的网络连接速度和计算机性能。 6. 配置Pentaho服务器:WebSpoon需要一个Pentaho服务器来运行。按照Pentaho服务器的官方文档进行配置和安装。 7. 启动WebSpoon:当安装和配置完成后,在命令行中执行命令 "mvn tomcat7:run" 来启动WebSpoon。等待一段时间,直到命令行中显示“Tomcat started on port(s): 8080”或类似的信息。 8. 访问WebSpoon:打开Web浏览器,输入URL "http://localhost:8080/spoon" 来访问WebSpoon。现在,你应该能够在浏览器中看到WebSpoon的主界面,并使用它进行数据分析和可视化。 请注意,这只是WebSpoon源码运行的一般步骤,具体情况可能因为操作系统和软件版本的不同而有所不同。为了成功运行WebSpoon,建议参考WebSpoon项目的官方文档和说明进行安装和配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值