http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/src/main/js/views/templates/getting-started.jade ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/views/templates/getting-started.jade b/modules/web-console/src/main/js/views/templates/getting-started.jade deleted file mode 100644 index 98bc265..0000000 --- a/modules/web-console/src/main/js/views/templates/getting-started.jade +++ /dev/null @@ -1,32 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -.modal.center(role='dialog') - .modal-dialog - .modal-content - #errors-container.modal-header.header - button.close(ng-click='close()' aria-hidden='true') × - h4.modal-title {{title}} - .getting-started - .col-xs-12(ng-bind-html='message') - .modal-footer - .checkbox - label - input(type='checkbox' ng-model='ui.showGettingStarted') - | Show getting started on next login - a.btn.btn-primary(ng-disabled='isFirst()' ng-click='!isFirst() && prev()') Prev - a.btn.btn-primary(ng-disabled='isLast()' ng-click='!isLast() && next()') Next - a.btn.btn-primary(ng-click='close()') Close
http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/src/main/js/views/templates/message.jade ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/views/templates/message.jade b/modules/web-console/src/main/js/views/templates/message.jade deleted file mode 100644 index 6dcf445..0000000 --- a/modules/web-console/src/main/js/views/templates/message.jade +++ /dev/null @@ -1,26 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -.modal(tabindex='-1' role='dialog') - .modal-dialog - .modal-content - .modal-header - button.close(ng-click='$hide()' aria-hidden='true') × - h4.modal-title {{title}} - .modal-body(ng-show='content') - p(ng-bind-html='content.join("<br/>")' style='text-align: left;') - .modal-footer - button.btn.btn-primary(id='confirm-btn-confirm' ng-click='$hide()') Ok http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/src/main/js/views/templates/pagination.jade ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/views/templates/pagination.jade b/modules/web-console/src/main/js/views/templates/pagination.jade deleted file mode 100644 index 08ced60..0000000 --- a/modules/web-console/src/main/js/views/templates/pagination.jade +++ /dev/null @@ -1,32 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -nav(ng-if='numPages && pages.length >= 2') - ul.pagination - li(ng-if='currentPage > 1') - a(href='javascript:void(0);' ng-click='selectPage(1)' bs-tooltip='' data-title='First page' data-placement='bottom') - i.fa.fa-angle-double-left - li(ng-if='currentPage > 1') - a(href='javascript:void(0);' ng-click='selectPage(currentPage-1)' bs-tooltip='' data-title='Previous page' data-placement='bottom') - i.fa.fa-angle-left - li(ng-repeat='page in pages' ng-class='{active: page==currentPage}') - a(href='javascript:void(0);' ng-click='selectPage(page)') {{page}} - li(ng-if='currentPage < numPages') - a(href='javascript:void(0);' ng-click='selectPage(currentPage+1)' bs-tooltip='' data-title='Next page' data-placement='bottom') - i.fa.fa-angle-right - li(ng-if='currentPage < numPages') - a(href='javascript:void(0);' ng-click='selectPage(numPages)' bs-tooltip='' data-title='Last page' data-placement='bottom') - i.fa.fa-angle-double-right \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/src/main/js/views/templates/select.jade ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/views/templates/select.jade b/modules/web-console/src/main/js/views/templates/select.jade deleted file mode 100644 index 3feee61..0000000 --- a/modules/web-console/src/main/js/views/templates/select.jade +++ /dev/null @@ -1,26 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -ul.select.dropdown-menu(tabindex='-1' ng-show='$isVisible()' role='select') - li(ng-if='$showAllNoneButtons || ($isMultiple && $matches.length > 2)') - a(id='li-dropdown-select-all' ng-click='$selectAll()') {{$allText}} ({{$matches.length}}) - a(id='li-dropdown-select-none' ng-click='$selectNone()') {{$noneText}} - hr(style='margin: 5px 0') - li(role='presentation' ng-repeat='match in $matches') - hr(ng-if='match.value == undefined' style='margin: 5px 0') - a(id='li-dropdown-item-{{$index}}' role='menuitem' tabindex='-1' ng-class='{active: $isActive($index)}' ng-click='$select($index, $event)' bs-tooltip='widthIsSufficient && !widthIsSufficient("li-dropdown-item-{{$index}}", $index, match.label) ? match.label : ""' data-placement='bottom') - i(class='{{$iconCheckmark}}' ng-if='$isActive($index)' ng-class='{active: $isActive($index)}') - span(ng-bind='match.label') http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/src/main/js/views/templates/validation-error.jade ---------------------------------------------------------------------- diff --git a/modules/web-console/src/main/js/views/templates/validation-error.jade b/modules/web-console/src/main/js/views/templates/validation-error.jade deleted file mode 100644 index 13deb9b..0000000 --- a/modules/web-console/src/main/js/views/templates/validation-error.jade +++ /dev/null @@ -1,25 +0,0 @@ -//- - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -.popover.validation-error - .arrow - .popover-content - table - tr - td - label {{content}}   - td - button.close(id='popover-btn-close' ng-click='$hide()') × http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/src/test/js/routes/agent.js ---------------------------------------------------------------------- diff --git a/modules/web-console/src/test/js/routes/agent.js b/modules/web-console/src/test/js/routes/agent.js deleted file mode 100644 index 1c4aff5..0000000 --- a/modules/web-console/src/test/js/routes/agent.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var request = require('supertest'), - should = require('should'), - app = require('../../app'), - fs = require('fs'), - https = require('https'), - config = require('../../helpers/configuration-loader.js'), - agentManager = require('../../agents/agent-manager'); - -/** - * Create HTTP server. - */ -/** - * Start agent server. - */ -var agentServer = https.createServer({ - key: fs.readFileSync(config.get('monitor:server:key')), - cert: fs.readFileSync(config.get('monitor:server:cert')), - passphrase: config.get('monitor:server:keyPassphrase') -}); - -agentServer.listen(config.get('monitor:server:port')); - -agentManager.createManager(agentServer); - -describe('request from agent', function() { - var agent = request.agent(app); - - before(function (done) { - this.timeout(10000); - - agent - .post('/login') - .send({email: '[email protected]', password: 'test'}) - .expect(302) - .end(function (err) { - if (error) - throw error; - - setTimeout(done, 5000); - }); - }); - - it('should return topology snapshot', function(done){ - agent - .post('/agent/topology') - .send({}) - .end(function(err, nodes) { - if (error) { - console.log(error.response.text); - - throw error; - } - - console.log(nodes); - - done(); - }); - }); - - //it('should query result', function(done){ - // agent - // .post('/agent/query') - // .send({ - // username: 'nva', - // password: 'nva.141', - // host: 'localhost', - // port: '5432', - // dbName: 'ggmonitor' - // }) - // .end(function(err, res) { - // if (err) - // throw err; - // - // done(); - // }); - //}); -}); http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/.gitignore ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/.gitignore b/modules/web-console/web-agent/.gitignore new file mode 100644 index 0000000..57dd45e --- /dev/null +++ b/modules/web-console/web-agent/.gitignore @@ -0,0 +1,2 @@ +logs/*.log.* +jdbc-drivers/*.jar http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/README.txt ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/README.txt b/modules/web-console/web-agent/README.txt new file mode 100644 index 0000000..c6e625b --- /dev/null +++ b/modules/web-console/web-agent/README.txt @@ -0,0 +1,88 @@ +Ignite Web Agent +====================================== +Ignite Web Agent is a java standalone application that allow to connect Ignite Grid to Ignite Web Console. +Ignite Web Agent communicates with grid nodes via REST interface and connects to Ignite Web Console via web-socket. + +Two main functions of Ignite Web Agent: + 1. Proxy between Ignite Web Console and Ignite Grid to execute SQL statements and collect metrics for monitoring. + You may need to specify URI for connect to Ignite REST server via "-n" option. + + 2. Proxy between Ignite Web Console and user RDBMS to collect database metadata for later CacheTypeMetadata configuration. + You may need to copy JDBC driver into "./jdbc-drivers" subfolder or specify path via "-d" option. + +Usage example: + ignite-web-agent.sh + +Configuration file: + Should be a file with simple line-oriented format as described here: http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load(java.io.Reader) + + Available entries names: + tokens + server-uri + node-uri + driver-folder + + Example configuration file: + tokens=1a2b3c4d5f,2j1s134d12 + serverURI=https://console.example.com:3001 + +Security tokens: + 1) By default security token of current user will be included into "default.properties" inside downloaded "ignite-web-agent-x.x.x.zip". + 2) One can get/reset token in Web Console profile (https://<your_console_address>/settings/profile). + 3) One may specify several comma separated tokens using configuration file or command line arguments of web agent. + +Ignite Web agent requirements: + 1) In order to communicate with web agent Ignite node should be started with REST server (move ignite-rest-http folder from lib/optional/ to lib/). + 2) Configure web agent serverURI property by Ignite node REST server URI. + +Options: + -h, --help + Print this help message + -c, --config + Path to configuration file + -d, --driver-folder + Path to folder with JDBC drivers, default value: ./jdbc-drivers + -n, --node-uri + URI for connect to Ignite REST server, default value: + http://localhost:8080 + -s, --server-uri + URI for connect to Ignite Web Console via web-socket protocol, default + value: http://localhost:3001 + -t, --tokens + User's security tokens + +How to build: + To build from sources run following command in Ignite project root folder: + mvn clean package -pl :ignite-web-agent -am -P web-console -DskipTests=true + +Demo of Ignite Web Agent: + In order to simplify evaluation demo mode was implemented. To start demo, you need to click button "Start demo". + New tab will be open with prepared demo data. + + 1) Demo for import domain model from database. + In this mode an in-memory H2 database will be started. + How to evaluate: + 1.1) Go to Ignite Web Console "Domain model" screen. + 1.2) Click "Import from database". You should see modal with demo description. + 1.3) Click "Next" button. You should see list of available schemas. + 1.4) Click "Next" button. You should see list of available tables. + 1.5) Click "Next" button. You should see import options. + 1.6) Select some of them and click "Save". + + 2) Demo for SQL. + How to evaluate: + In this mode internal Ignite node will be started. Cache created and populated with data. + 2.1) Click "SQL" in Ignite Web Console top menu. + 2.2) "Demo" notebook with preconfigured queries will be opened. + 2.3) You can also execute any SQL queries for tables: "Country, Department, Employee, Parking, Car". + + For example: + 2.4) Enter SQL statement: + SELECT p.name, count(*) AS cnt + FROM "ParkingCache".Parking p + INNER JOIN "CarCache".Car c + ON (p.id) = (c.parkingId) + GROUP BY P.NAME + 2.5) Click "Execute" button. You should get some data in table. + 2.6) Click charts buttons to see auto generated charts. + http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/assembly/release-web-agent.xml ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/assembly/release-web-agent.xml b/modules/web-console/web-agent/assembly/release-web-agent.xml new file mode 100644 index 0000000..bb994c0 --- /dev/null +++ b/modules/web-console/web-agent/assembly/release-web-agent.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd"> + <id>release-ignite-web-agent</id> + + <formats> + <format>zip</format> + </formats> + + <fileSets> + <fileSet> + <directory>${basedir}</directory> + <outputDirectory>/</outputDirectory> + <includes> + <include>jdbc-drivers/README*</include> + <include>demo/README*</include> + <include>demo/*.sql</include> + <include>logs/README*</include> + <include>README*</include> + <include>LICENSE*</include> + <include>NOTICE*</include> + </includes> + </fileSet> + <fileSet> + <directory>${basedir}/bin</directory> + <outputDirectory>/</outputDirectory> + <includes> + <include>**/*.bat</include> + </includes> + </fileSet> + <fileSet> + <directory>${basedir}/bin</directory> + <outputDirectory>/</outputDirectory> + <fileMode>0755</fileMode> + <includes> + <include>**/*.sh</include> + </includes> + </fileSet> + <fileSet> + <directory>${project.build.directory}</directory> + <outputDirectory>/</outputDirectory> + <includes> + <include>ignite-web-agent-${project.version}.jar</include> + </includes> + </fileSet> + </fileSets> +</assembly> http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/bin/ignite-web-agent.bat ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/bin/ignite-web-agent.bat b/modules/web-console/web-agent/bin/ignite-web-agent.bat new file mode 100644 index 0000000..f16eb35 --- /dev/null +++ b/modules/web-console/web-agent/bin/ignite-web-agent.bat @@ -0,0 +1,70 @@ +:: +:: Licensed to the Apache Software Foundation (ASF) under one or more +:: contributor license agreements. See the NOTICE file distributed with +:: this work for additional information regarding copyright ownership. +:: The ASF licenses this file to You under the Apache License, Version 2.0 +:: (the "License"); you may not use this file except in compliance with +:: the License. You may obtain a copy of the License at +:: +:: http://www.apache.org/licenses/LICENSE-2.0 +:: +:: Unless required by applicable law or agreed to in writing, software +:: distributed under the License is distributed on an "AS IS" BASIS, +:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +:: See the License for the specific language governing permissions and +:: limitations under the License. +:: + +@echo off +Setlocal EnableDelayedExpansion + +if "%OS%" == "Windows_NT" setlocal + +:: Check JAVA_HOME. +if defined JAVA_HOME goto checkJdk + echo %0, ERROR: + echo JAVA_HOME environment variable is not found. + echo Please point JAVA_HOME variable to location of JDK 1.7 or JDK 1.8. + echo You can also download latest JDK at http://java.com/download. +goto error_finish + +:checkJdk +:: Check that JDK is where it should be. +if exist "%JAVA_HOME%\bin\java.exe" goto checkJdkVersion + echo %0, ERROR: + echo JAVA is not found in JAVA_HOME=%JAVA_HOME%. + echo Please point JAVA_HOME variable to installation of JDK 1.7 or JDK 1.8. + echo You can also download latest JDK at http://java.com/download. +goto error_finish + +:checkJdkVersion +"%JAVA_HOME%\bin\java.exe" -version 2>&1 | findstr "1\.[78]\." > nul +if %ERRORLEVEL% equ 0 goto run_java + echo %0, ERROR: + echo The version of JAVA installed in %JAVA_HOME% is incorrect. + echo Please point JAVA_HOME variable to installation of JDK 1.7 or JDK 1.8. + echo You can also download latest JDK at http://java.com/download. +goto error_finish + +:run_java + +:: +:: JVM options. See http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp for more details. +:: +:: ADD YOUR/CHANGE ADDITIONAL OPTIONS HERE +:: +if "%JVM_OPTS%" == "" set JVM_OPTS=-Xms1g -Xmx1g -server -XX:+AggressiveOpts -XX:MaxPermSize=256m + +"%JAVA_HOME%\bin\java.exe" %JVM_OPTS% -cp "*" org.apache.ignite.console.agent.AgentLauncher %* + +set JAVA_ERRORLEVEL=%ERRORLEVEL% + +:: errorlevel 130 if aborted with Ctrl+c +if %JAVA_ERRORLEVEL%==130 goto eof + +:error_finish + +if not "%NO_PAUSE%" == "1" pause + +goto :eof + http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/bin/ignite-web-agent.sh ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/bin/ignite-web-agent.sh b/modules/web-console/web-agent/bin/ignite-web-agent.sh new file mode 100644 index 0000000..3f2c2bc --- /dev/null +++ b/modules/web-console/web-agent/bin/ignite-web-agent.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Check JAVA_HOME. +if [ "$JAVA_HOME" = "" ]; then + JAVA=`type -p java` + RETCODE=$? + + if [ $RETCODE -ne 0 ]; then + echo $0", ERROR:" + echo "JAVA_HOME environment variable is not found." + echo "Please point JAVA_HOME variable to location of JDK 1.7 or JDK 1.8." + echo "You can also download latest JDK at http://java.com/download" + + exit 1 + fi + + JAVA_HOME= +else + JAVA=${JAVA_HOME}/bin/java +fi + +# +# Check JDK. +# +if [ ! -e "$JAVA" ]; then + echo $0", ERROR:" + echo "JAVA is not found in JAVA_HOME=$JAVA_HOME." + echo "Please point JAVA_HOME variable to installation of JDK 1.7 or JDK 1.8." + echo "You can also download latest JDK at http://java.com/download" + + exit 1 +fi + +JAVA_VER=`"$JAVA" -version 2>&1 | egrep "1\.[78]\."` + +if [ "$JAVA_VER" == "" ]; then + echo $0", ERROR:" + echo "The version of JAVA installed in JAVA_HOME=$JAVA_HOME is incorrect." + echo "Please point JAVA_HOME variable to installation of JDK 1.7 or JDK 1.8." + echo "You can also download latest JDK at http://java.com/download" + + exit 1 +fi + +SOURCE="${BASH_SOURCE[0]}" + +DIR="$( dirname "$SOURCE" )" + +while [ -h "$SOURCE" ] + do + SOURCE="$(readlink "$SOURCE")" + + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" + + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + done + +DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + +cd $DIR + +# +# JVM options. See http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp for more details. +# +# ADD YOUR/CHANGE ADDITIONAL OPTIONS HERE +# +if [ -z "$JVM_OPTS" ] ; then + JVM_OPTS="-Xms1g -Xmx1g -server -XX:+AggressiveOpts -XX:MaxPermSize=256m" +fi + +"$JAVA" ${JVM_OPTS} -cp "*" org.apache.ignite.console.agent.AgentLauncher "$@" http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/demo/README.txt ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/demo/README.txt b/modules/web-console/web-agent/demo/README.txt new file mode 100644 index 0000000..17e5074 --- /dev/null +++ b/modules/web-console/web-agent/demo/README.txt @@ -0,0 +1,4 @@ +Ignite Web Agent +====================================== + +This is folder for demo files. http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/demo/db-init.sql ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/demo/db-init.sql b/modules/web-console/web-agent/demo/db-init.sql new file mode 100644 index 0000000..0688ea0 --- /dev/null +++ b/modules/web-console/web-agent/demo/db-init.sql @@ -0,0 +1,102 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +CREATE TABLE COUNTRY ( + ID INTEGER NOT NULL PRIMARY KEY, + NAME VARCHAR(50), + POPULATION INTEGER NOT NULL +); + +CREATE TABLE DEPARTMENT ( + ID INTEGER NOT NULL PRIMARY KEY, + COUNTRY_ID INTEGER NOT NULL, + NAME VARCHAR(50) NOT NULL +); + +CREATE TABLE EMPLOYEE ( + ID INTEGER NOT NULL PRIMARY KEY, + DEPARTMENT_ID INTEGER NOT NULL, + MANAGER_ID INTEGER, + FIRST_NAME VARCHAR(50) NOT NULL, + LAST_NAME VARCHAR(50) NOT NULL, + EMAIL VARCHAR(50) NOT NULL, + PHONE_NUMBER VARCHAR(50), + HIRE_DATE DATE NOT NULL, + JOB VARCHAR(50) NOT NULL, + SALARY DOUBLE +); + +CREATE INDEX EMP_SALARY ON EMPLOYEE (SALARY ASC); +CREATE INDEX EMP_NAMES ON EMPLOYEE (FIRST_NAME ASC, LAST_NAME ASC); + +CREATE SCHEMA CARS; + +CREATE TABLE CARS.PARKING ( + ID INTEGER NOT NULL PRIMARY KEY, + NAME VARCHAR(50) NOT NULL, + CAPACITY INTEGER NOT NULL +); + +CREATE TABLE CARS.CAR ( + ID INTEGER NOT NULL PRIMARY KEY, + PARKING_ID INTEGER NOT NULL, + NAME VARCHAR(50) NOT NULL +); + +INSERT INTO COUNTRY(ID, NAME, POPULATION) VALUES(0, 'Country #1', 10000000); +INSERT INTO COUNTRY(ID, NAME, POPULATION) VALUES(1, 'Country #2', 20000000); +INSERT INTO COUNTRY(ID, NAME, POPULATION) VALUES(2, 'Country #3', 30000000); + +INSERT INTO DEPARTMENT(ID, COUNTRY_ID, NAME) VALUES(0, 0, 'Department #1'); +INSERT INTO DEPARTMENT(ID, COUNTRY_ID, NAME) VALUES(1, 0, 'Department #2'); +INSERT INTO DEPARTMENT(ID, COUNTRY_ID, NAME) VALUES(2, 2, 'Department #3'); +INSERT INTO DEPARTMENT(ID, COUNTRY_ID, NAME) VALUES(3, 1, 'Department #4'); +INSERT INTO DEPARTMENT(ID, COUNTRY_ID, NAME) VALUES(4, 1, 'Department #5'); +INSERT INTO DEPARTMENT(ID, COUNTRY_ID, NAME) VALUES(5, 1, 'Department #6'); + +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(0, 0, 'First name manager #1', 'Last name manager #1', 'Email manager #1', 'Phone number manager #1', '2014-01-01', 'Job manager #1', 1100.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(1, 1, 'First name manager #2', 'Last name manager #2', 'Email manager #2', 'Phone number manager #2', '2014-01-01', 'Job manager #2', 2100.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(2, 2, 'First name manager #3', 'Last name manager #3', 'Email manager #3', 'Phone number manager #3', '2014-01-01', 'Job manager #3', 3100.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(3, 3, 'First name manager #4', 'Last name manager #4', 'Email manager #4', 'Phone number manager #4', '2014-01-01', 'Job manager #4', 1500.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(4, 4, 'First name manager #5', 'Last name manager #5', 'Email manager #5', 'Phone number manager #5', '2014-01-01', 'Job manager #5', 1700.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(5, 5, 'First name manager #6', 'Last name manager #6', 'Email manager #6', 'Phone number manager #6', '2014-01-01', 'Job manager #6', 1300.00); + +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(101, 0, 0, 'First name employee #1', 'Last name employee #1', 'Email employee #1', 'Phone number employee #1', '2014-01-01', 'Job employee #1', 600.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(102, 0, 0, 'First name employee #2', 'Last name employee #2', 'Email employee #2', 'Phone number employee #2', '2014-01-01', 'Job employee #2', 1600.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(103, 1, 1, 'First name employee #3', 'Last name employee #3', 'Email employee #3', 'Phone number employee #3', '2014-01-01', 'Job employee #3', 2600.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(104, 2, 2, 'First name employee #4', 'Last name employee #4', 'Email employee #4', 'Phone number employee #4', '2014-01-01', 'Job employee #4', 1000.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(105, 2, 2, 'First name employee #5', 'Last name employee #5', 'Email employee #5', 'Phone number employee #5', '2014-01-01', 'Job employee #5', 1200.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(106, 2, 2, 'First name employee #6', 'Last name employee #6', 'Email employee #6', 'Phone number employee #6', '2014-01-01', 'Job employee #6', 800.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(107, 3, 3, 'First name employee #7', 'Last name employee #7', 'Email employee #7', 'Phone number employee #7', '2014-01-01', 'Job employee #7', 1400.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(108, 4, 4, 'First name employee #8', 'Last name employee #8', 'Email employee #8', 'Phone number employee #8', '2014-01-01', 'Job employee #8', 800.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(109, 4, 4, 'First name employee #9', 'Last name employee #9', 'Email employee #9', 'Phone number employee #9', '2014-01-01', 'Job employee #9', 1490.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(110, 4, 4, 'First name employee #10', 'Last name employee #12', 'Email employee #10', 'Phone number employee #10', '2014-01-01', 'Job employee #10', 1600.00); +INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(111, 5, 5, 'First name employee #11', 'Last name employee #11', 'Email employee #11', 'Phone number employee #11', '2014-01-01', 'Job employee #11', 400.00); + +INSERT INTO CARS.PARKING(ID, NAME, CAPACITY) VALUES(0, 'Parking #1', 10); +INSERT INTO CARS.PARKING(ID, NAME, CAPACITY) VALUES(1, 'Parking #2', 20); +INSERT INTO CARS.PARKING(ID, NAME, CAPACITY) VALUES(2, 'Parking #3', 30); + +INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(0, 0, 'Car #1'); +INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(1, 0, 'Car #2'); +INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(2, 0, 'Car #3'); +INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(3, 1, 'Car #4'); +INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(4, 1, 'Car #5'); +INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(5, 2, 'Car #6'); +INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(6, 2, 'Car #7'); +INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(7, 2, 'Car #8'); +INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(8, 2, 'Car #9'); http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/jdbc-drivers/README.txt ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/jdbc-drivers/README.txt b/modules/web-console/web-agent/jdbc-drivers/README.txt new file mode 100644 index 0000000..cad43b7 --- /dev/null +++ b/modules/web-console/web-agent/jdbc-drivers/README.txt @@ -0,0 +1,10 @@ +Ignite Web Agent +====================================== + +If you are are planning to load cache type metadata from your existing databases +you need to copy JDBC drivers in this folder. + +This is default folder for JDBC drivers. + +Also, you could specify custom folder using option: "-d CUSTOM_PATH_TO_FOLDER_WITH_JDBC_DRIVERS". + http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/logs/README.txt ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/logs/README.txt b/modules/web-console/web-agent/logs/README.txt new file mode 100644 index 0000000..3a220eb --- /dev/null +++ b/modules/web-console/web-agent/logs/README.txt @@ -0,0 +1,5 @@ +Ignite Web Agent +====================================== + +This is folder for agent logs. + http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/pom.xml ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/pom.xml b/modules/web-console/web-agent/pom.xml new file mode 100644 index 0000000..530a272 --- /dev/null +++ b/modules/web-console/web-agent/pom.xml @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- + POM file. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-parent</artifactId> + <version>1</version> + <relativePath>../../../parent</relativePath> + </parent> + + <artifactId>ignite-web-agent</artifactId> + <packaging>jar</packaging> + <version>1.7.0-SNAPSHOT</version> + <url>http://ignite.apache.org</url> + + <properties> + <maven.build.timestamp.format>yyMMddHHmmss</maven.build.timestamp.format> + </properties> + + <dependencies> + <dependency> + <groupId>io.socket</groupId> + <artifactId>socket.io-client</artifactId> + <version>0.7.0</version> + </dependency> + + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-json-org</artifactId> + <version>${jackson2.version}</version> + </dependency> + + <dependency> + <groupId>com.beust</groupId> + <artifactId>jcommander</artifactId> + <version>1.48</version> + </dependency> + + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + <version>${httpclient.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-schema-import-db</artifactId> + <version>${project.version}</version> + <exclusions> + <exclusion> + <groupId>org.gridgain</groupId> + <artifactId>ignite-shmem</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-indexing</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-rest-http</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-spring</artifactId> + <version>${project.version}</version> + <exclusions> + <exclusion> + <groupId>org.springframework</groupId> + <artifactId>spring-aop</artifactId> + </exclusion> + <exclusion> + <groupId>org.springframework</groupId> + <artifactId>spring-tx</artifactId> + </exclusion> + <exclusion> + <groupId>org.springframework</groupId> + <artifactId>spring-jdbc</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>org.apache.ignite</groupId> + <artifactId>ignite-log4j</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + + <build> + <finalName>ignite-web-agent-${project.version}</finalName> + + <plugins> + <plugin> + <artifactId>maven-jar-plugin</artifactId> + <version>2.5</version> + + <configuration> + <archive> + <manifest> + <mainClass>org.apache.ignite.console.agent.AgentLauncher</mainClass> + </manifest> + <manifestEntries> + <Build-Time>${maven.build.timestamp}</Build-Time> + </manifestEntries> + </archive> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.4</version> + + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + + <configuration> + <createDependencyReducedPom>false</createDependencyReducedPom> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/maven/**</exclude> + </excludes> + </filter> + </filters> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <version>2.4</version> + <inherited>false</inherited> + + <executions> + <execution> + <id>release-web-agent</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + <configuration> + <descriptors> + <descriptor>assembly/release-web-agent.xml</descriptor> + </descriptors> + <finalName>ignite-web-agent-${project.version}</finalName> + <outputDirectory>target</outputDirectory> + <appendAssemblyId>false</appendAssemblyId> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project> http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java new file mode 100644 index 0000000..d4787cc --- /dev/null +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.console.agent; + +import com.beust.jcommander.Parameter; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +/** + * Agent configuration. + */ +public class AgentConfiguration { + /** Default server port. */ + public static final int DFLT_SERVER_PORT = 3001; + + /** Default Ignite node HTTP port. */ + public static final int DFLT_NODE_PORT = 8080; + + /** Default path to agent property file. */ + public static final String DFLT_CFG_PATH = "default.properties"; + + /** Default server URI. */ + private static final String DFLT_SERVER_URI = "http://localhost:3001"; + + /** Default Ignite node HTTP URI. */ + private static final String DFLT_NODE_URI = "http://localhost:8080"; + + /** */ + @Parameter(names = {"-t", "--tokens"}, + description = "User's tokens separated by comma used to connect to Ignite Console.") + private List<String> tokens; + + /** */ + @Parameter(names = {"-s", "--server-uri"}, + description = "URI for connect to Ignite Console via web-socket protocol" + + " " + + " Default value: " + DFLT_SERVER_URI) + private String srvUri; + + /** */ + @Parameter(names = {"-n", "--node-uri"}, description = "URI for connect to Ignite node REST server" + + " " + + " Default value: " + DFLT_NODE_URI) + private String nodeUri; + + /** URI for connect to Ignite demo node REST server */ + private String demoNodeUri; + + /** */ + @Parameter(names = {"-c", "--config"}, description = "Path to agent property file" + + " " + + " Default value: " + DFLT_CFG_PATH) + private String cfgPath; + + /** */ + @Parameter(names = {"-d", "--driver-folder"}, description = "Path to folder with JDBC drivers" + + " " + + " Default value: ./jdbc-drivers") + private String driversFolder; + + /** */ + @Parameter(names = { "-h", "--help" }, help = true, description = "Print this help message") + private Boolean help; + + /** + * @return Tokens. + */ + public List<String> tokens() { + return tokens; + } + + /** + * @param tokens Tokens. + */ + public void tokens(List<String> tokens) { + this.tokens = tokens; + } + + /** + * @return Server URI. + */ + public String serverUri() { + return srvUri; + } + + /** + * @param srvUri URI. + */ + public void serverUri(String srvUri) { + this.srvUri = srvUri; + } + + /** + * @return Node URI. + */ + public String nodeUri() { + return nodeUri; + } + + /** + * @param nodeUri Node URI. + */ + public void nodeUri(String nodeUri) { + this.nodeUri = nodeUri; + } + + /** + * @return Demo node URI. + */ + public String demoNodeUri() { + return demoNodeUri; + } + + /** + * @param demoNodeUri Demo node URI. + */ + public void demoNodeUri(String demoNodeUri) { + this.demoNodeUri = demoNodeUri; + } + + /** + * @return Configuration path. + */ + public String configPath() { + return cfgPath == null ? DFLT_CFG_PATH : cfgPath; + } + + /** + * @return Configured drivers folder. + */ + public String driversFolder() { + return driversFolder; + } + + /** + * @param driversFolder Driver folder. + */ + public void driversFolder(String driversFolder) { + this.driversFolder = driversFolder; + } + + /** + * @return {@code true} If agent options usage should be printed. + */ + public Boolean help() { + return help != null ? help : Boolean.FALSE; + } + + /** + * @param cfgUrl URL. + */ + public void load(URL cfgUrl) throws IOException { + Properties props = new Properties(); + + try (Reader reader = new InputStreamReader(cfgUrl.openStream())) { + props.load(reader); + } + + String val = (String)props.remove("tokens"); + + if (val != null) + tokens(Arrays.asList(val.split(","))); + + val = (String)props.remove("server-uri"); + + if (val != null) + serverUri(val); + + val = (String)props.remove("node-uri"); + + if (val != null) + nodeUri(val); + + val = (String)props.remove("driver-folder"); + + if (val != null) + driversFolder(val); + } + + /** + * @param cmd Command. + */ + public void merge(AgentConfiguration cmd) { + if (tokens == null) + tokens(cmd.tokens()); + + if (srvUri == null) + serverUri(cmd.serverUri()); + + if (srvUri == null) + serverUri(DFLT_SERVER_URI); + + if (nodeUri == null) + nodeUri(cmd.nodeUri()); + + if (nodeUri == null) + nodeUri(DFLT_NODE_URI); + + if (driversFolder == null) + driversFolder(cmd.driversFolder()); + } + + /** {@inheritDoc} */ + @Override public String toString() { + StringBuilder sb = new StringBuilder(); + + if (tokens != null && tokens.size() > 0) { + sb.append("User's security tokens : "); + + boolean first = true; + + for (String tok : tokens) { + if (first) + first = false; + else + sb.append(","); + + if (tok.length() > 4) { + sb.append(new String(new char[tok.length() - 4]).replace('\0', '*')); + + sb.append(tok.substring(tok.length() - 4)); + } + else + sb.append(new String(new char[tok.length()]).replace('\0', '*')); + } + + sb.append('\n'); + } + + sb.append("URI to Ignite node REST server: ").append(nodeUri == null ? DFLT_NODE_URI : nodeUri).append('\n'); + sb.append("URI to Ignite Console server : ").append(srvUri == null ? DFLT_SERVER_URI : srvUri).append('\n'); + sb.append("Path to agent property file : ").append(configPath()).append('\n'); + + String drvFld = driversFolder(); + + if (drvFld == null) { + File agentHome = AgentUtils.getAgentHome(); + + if (agentHome != null) + drvFld = new File(agentHome, "jdbc-drivers").getPath(); + } + + sb.append("Path to JDBC drivers folder : ").append(drvFld); + + return sb.toString(); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java new file mode 100644 index 0000000..810fad4 --- /dev/null +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.console.agent; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; +import io.socket.client.Ack; +import io.socket.client.IO; +import io.socket.client.Socket; +import io.socket.emitter.Emitter; +import java.io.File; +import java.io.IOException; +import java.net.ConnectException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.apache.ignite.console.agent.handlers.DatabaseHandler; +import org.apache.ignite.console.agent.handlers.RestHandler; +import org.apache.ignite.internal.util.typedef.X; +import org.apache.log4j.Logger; +import org.json.JSONException; +import org.json.JSONObject; + +import static io.socket.client.Socket.EVENT_CONNECT; +import static io.socket.client.Socket.EVENT_CONNECTING; +import static io.socket.client.Socket.EVENT_CONNECT_ERROR; +import static io.socket.client.Socket.EVENT_DISCONNECT; +import static io.socket.client.Socket.EVENT_ERROR; +import static io.socket.client.Socket.EVENT_RECONNECTING; +import static org.apache.ignite.console.agent.AgentConfiguration.DFLT_SERVER_PORT; + +/** + * Control Center Agent launcher. + */ +public class AgentLauncher { + /** */ + private static final Logger log = Logger.getLogger(AgentLauncher.class.getName()); + + /** */ + private static final String EVENT_NODE_REST = "node:rest"; + + /** */ + private static final String EVENT_SCHEMA_IMPORT_DRIVERS = "schemaImport:drivers"; + + /** */ + private static final String EVENT_SCHEMA_IMPORT_SCHEMAS = "schemaImport:schemas"; + + /** */ + private static final String EVENT_SCHEMA_IMPORT_METADATA = "schemaImport:metadata"; + + /** */ + private static final String EVENT_AGENT_WARNING = "agent:warning"; + + /** */ + private static final String EVENT_AGENT_CLOSE = "agent:close"; + + /** */ + private static final int RECONNECT_INTERVAL = 3000; + + /** + * Create a trust manager that trusts all certificates It is not using a particular keyStore + */ + private static TrustManager[] getTrustManagers() { + return new TrustManager[] { + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + }}; + } + + /** + * On error listener. + */ + private static final Emitter.Listener onError = new Emitter.Listener() { + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + @Override public void call(Object... args) { + Throwable e = (Throwable)args[0]; + + ConnectException ce = X.cause(e, ConnectException.class); + + if (ce != null) + log.error("Failed to receive response from server (connection refused)."); + else { + Exception ignore = X.cause(e, SSLHandshakeException.class); + + if (ignore != null) { + log.error("Failed to establish SSL connection to server, due to errors with SSL handshake."); + log.error("Add to environment variable JVM_OPTS parameter \"-Dtrust.all=true\" to skip certificate validation in case of using self-signed certificate."); + + System.exit(1); + } + + ignore = X.cause(e, IOException.class); + + if (ignore != null && "404".equals(ignore.getMessage())) { + log.error("Failed to receive response from server (connection refused)."); + + return; + } + + log.error("Connection error.", e); + } + } + }; + + /** + * On disconnect listener. + */ + private static final Emitter.Listener onDisconnect = new Emitter.Listener() { + @Override public void call(Object... args) { + log.error(String.format("Connection closed: %s.", args)); + } + }; + + /** + * @param args Args. + */ + @SuppressWarnings("BusyWait") + public static void main(String[] args) throws Exception { + log.info("Starting Apache Ignite Web Console Agent..."); + + final AgentConfiguration cfg = new AgentConfiguration(); + + JCommander jCommander = new JCommander(cfg); + + String osName = System.getProperty("os.name").toLowerCase(); + + jCommander.setProgramName("ignite-web-agent." + (osName.contains("win") ? "bat" : "sh")); + + try { + jCommander.parse(args); + } + catch (ParameterException pe) { + log.error("Failed to parse command line parameters: " + Arrays.toString(args), pe); + + jCommander.usage(); + + return; + } + + String prop = cfg.configPath(); + + AgentConfiguration propCfg = new AgentConfiguration(); + + try { + File f = AgentUtils.resolvePath(prop); + + if (f == null) + log.warn("Failed to find agent property file: " + prop); + else + propCfg.load(f.toURI().toURL()); + } + catch (IOException ignore) { + if (!AgentConfiguration.DFLT_CFG_PATH.equals(prop)) + log.warn("Failed to load agent property file: " + prop, ignore); + } + + cfg.merge(propCfg); + + if (cfg.help()) { + jCommander.usage(); + + return; + } + + System.out.println(); + System.out.println("Agent configuration:"); + System.out.println(cfg); + System.out.println(); + + if (cfg.tokens() == null) { + String webHost; + + try { + webHost = new URI(cfg.serverUri()).getHost(); + } + catch (URISyntaxException e) { + log.error("Failed to parse Ignite Web Console uri", e); + + return; + } + + System.out.println("Security token is required to establish connection to the web console."); + System.out.println(String.format("It is available on the Profile page: https://%s/profile", webHost)); + + System.out.print("Enter security tokens separated by comma: "); + + cfg.tokens(Arrays.asList(System.console().readLine().trim().split(","))); + } + + final RestHandler restHnd = new RestHandler(cfg); + + try { + restHnd.start(); + + URI uri = URI.create(cfg.serverUri()); + + if (uri.getPort() == -1) + uri = URI.create(cfg.serverUri() + ':' + DFLT_SERVER_PORT); + + IO.Options opts = new IO.Options(); + + opts.reconnectionDelay = RECONNECT_INTERVAL; + + // Workaround for use self-signed certificate + if (Boolean.getBoolean("trust.all")) { + SSLContext ctx = SSLContext.getInstance("TLS"); + + // Create an SSLContext that uses our TrustManager + ctx.init(null, getTrustManagers(), null); + + opts.sslContext = ctx; + } + + final Socket client = IO.socket(uri, opts); + + try { + Emitter.Listener onConnecting = new Emitter.Listener() { + @Override public void call(Object... args) { + log.info("Connecting to: " + cfg.serverUri()); + } + }; + + Emitter.Listener onConnect = new Emitter.Listener() { + @Override public void call(Object... args) { + log.info("Connection established."); + + JSONObject authMsg = new JSONObject(); + + try { + authMsg.put("tokens", cfg.tokens()); + + String clsName = AgentLauncher.class.getSimpleName() + ".class"; + + String clsPath = AgentLauncher.class.getResource(clsName).toString(); + + if (clsPath.startsWith("jar")) { + String manifestPath = clsPath.substring(0, clsPath.lastIndexOf('!') + 1) + + "/META-INF/MANIFEST.MF"; + + Manifest manifest = new Manifest(new URL(manifestPath).openStream()); + + Attributes attr = manifest.getMainAttributes(); + + authMsg.put("ver", attr.getValue("Implementation-Version")); + authMsg.put("bt", attr.getValue("Build-Time")); + } + + client.emit("agent:auth", authMsg, new Ack() { + @Override public void call(Object... args) { + // Authentication failed if response contains args. + if (args != null && args.length > 0) { + onDisconnect.call(args); + + System.exit(1); + } + + log.info("Authentication success."); + } + }); + } + catch (JSONException | IOException e) { + log.error("Failed to construct authentication message", e); + + client.close(); + } + } + }; + + DatabaseHandler dbHnd = new DatabaseHandler(cfg); + + final CountDownLatch latch = new CountDownLatch(1); + + client + .on(EVENT_CONNECTING, onConnecting) + .on(EVENT_CONNECT, onConnect) + .on(EVENT_CONNECT_ERROR, onError) + .on(EVENT_RECONNECTING, onConnecting) + .on(EVENT_NODE_REST, restHnd) + .on(EVENT_SCHEMA_IMPORT_DRIVERS, dbHnd.availableDriversListener()) + .on(EVENT_SCHEMA_IMPORT_SCHEMAS, dbHnd.schemasListener()) + .on(EVENT_SCHEMA_IMPORT_METADATA, dbHnd.metadataListener()) + .on(EVENT_ERROR, onError) + .on(EVENT_DISCONNECT, onDisconnect) + .on(EVENT_AGENT_WARNING, new Emitter.Listener() { + @Override public void call(Object... args) { + log.warn(args[0]); + } + }) + .on(EVENT_AGENT_CLOSE, new Emitter.Listener() { + @Override public void call(Object... args) { + onDisconnect.call(args); + + client.off(); + + latch.countDown(); + } + }); + + client.connect(); + + latch.await(); + } + finally { + client.close(); + } + } + finally { + restHnd.stop(); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java new file mode 100644 index 0000000..50a849a --- /dev/null +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.console.agent; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.ProtectionDomain; +import org.apache.log4j.Logger; + +/** + * Utility methods. + */ +public class AgentUtils { + /** */ + private static final Logger log = Logger.getLogger(AgentUtils.class.getName()); + + /** + * Default constructor. + */ + private AgentUtils() { + // No-op. + } + + /** + * @param path Path to normalize. + * @return Normalized file path. + */ + public static String normalizePath(String path) { + return path != null ? path.replace('\\', '/') : null; + } + + /** + * @return App folder. + */ + public static File getAgentHome() { + try { + ProtectionDomain domain = AgentLauncher.class.getProtectionDomain(); + + // Should not happen, but to make sure our code is not broken. + if (domain == null || domain.getCodeSource() == null || domain.getCodeSource().getLocation() == null) { + log.warn("Failed to resolve agent jar location!"); + + return null; + } + + // Resolve path to class-file. + URI classesUri = domain.getCodeSource().getLocation().toURI(); + + boolean win = System.getProperty("os.name").toLowerCase().contains("win"); + + // Overcome UNC path problem on Windows (http://www.tomergabel.com/JavaMishandlesUNCPathsOnWindows.aspx) + if (win && classesUri.getAuthority() != null) + classesUri = new URI(classesUri.toString().replace("file://", "file:/")); + + return new File(classesUri).getParentFile(); + } + catch (URISyntaxException | SecurityException ignored) { + log.warn("Failed to resolve agent jar location!"); + + return null; + } + } + + /** + * Gets file associated with path. + * <p> + * First check if path is relative to agent home. + * If not, check if path is absolute. + * If all checks fail, then {@code null} is returned. + * <p> + * + * @param path Path to resolve. + * @return Resolved path as file, or {@code null} if path cannot be resolved. + */ + public static File resolvePath(String path) { + assert path != null; + + File home = getAgentHome(); + + if (home != null) { + File file = new File(home, normalizePath(path)); + + if (file.exists()) + return file; + } + + // 2. Check given path as absolute. + File file = new File(path); + + if (file.exists()) + return file; + + return null; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java new file mode 100644 index 0000000..7e4e320 --- /dev/null +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.console.agent.handlers; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule; +import io.socket.client.Ack; +import io.socket.emitter.Emitter; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * Base class for web socket handlers. + */ +abstract class AbstractHandler implements Emitter.Listener { + /** JSON object mapper. */ + private static final ObjectMapper mapper = new ObjectMapper(); + + static { + JsonOrgModule module = new JsonOrgModule(); + + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + + mapper.registerModule(module); + } + + /** + * @param obj Object. + * @return {@link JSONObject} or {@link JSONArray}. + */ + private Object toJSON(Object obj) { + if (obj instanceof Iterable) + return mapper.convertValue(obj, JSONArray.class); + + return mapper.convertValue(obj, JSONObject.class); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override public final void call(Object... args) { + Ack cb = null; + + try { + if (args == null || args.length == 0) + throw new IllegalArgumentException("Missing arguments."); + + if (args.length > 2) + throw new IllegalArgumentException("Wrong arguments count, must be <= 2: " + Arrays.toString(args)); + + JSONObject lsnrArgs = null; + + if (args.length == 1) { + if (args[0] instanceof JSONObject) + lsnrArgs = (JSONObject)args[0]; + else if (args[0] instanceof Ack) + cb = (Ack)args[0]; + else + throw new IllegalArgumentException("Wrong type of argument, must be JSONObject or Ack: " + args[0]); + } + else { + if (args[0] != null && !(args[0] instanceof JSONObject)) + throw new IllegalArgumentException("Wrong type of argument, must be JSONObject: " + args[0]); + + if (!(args[1] instanceof Ack)) + throw new IllegalArgumentException("Wrong type of argument, must be Ack: " + args[1]); + + lsnrArgs = (JSONObject)args[0]; + + cb = (Ack)args[1]; + } + + Object res = execute(lsnrArgs == null ? Collections.emptyMap() : mapper.convertValue(lsnrArgs, Map.class)); + + if (cb != null) + cb.call(null, toJSON(res)); + } + catch (Exception e) { + if (cb != null) + cb.call(e, null); + } + } + + /** + * Execute command with specified arguments. + * + * @param args Map with method args. + */ + public abstract Object execute(Map<String, Object> args) throws Exception; +} http://git-wip-us.apache.org/repos/asf/ignite/blob/6af6560a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java new file mode 100644 index 0000000..02146d9 --- /dev/null +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.console.agent.handlers; + +import io.socket.emitter.Emitter; +import java.io.BufferedReader; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import org.apache.ignite.console.agent.AgentConfiguration; +import org.apache.ignite.console.demo.AgentMetadataDemo; +import org.apache.ignite.schema.parser.DbMetadataReader; +import org.apache.ignite.schema.parser.DbTable; +import org.apache.log4j.Logger; + +import static org.apache.ignite.console.agent.AgentUtils.resolvePath; + +/** + * API to extract database metadata. + */ +public class DatabaseHandler { + /** */ + private static final Logger log = Logger.getLogger(DatabaseHandler.class.getName()); + + /** */ + private final File driversFolder; + + /** + * @param cfg Config. + */ + public DatabaseHandler(AgentConfiguration cfg) { + driversFolder = resolvePath(cfg.driversFolder() == null ? "jdbc-drivers" : cfg.driversFolder()); + } + + /** + * @param jdbcDriverJarPath JDBC driver JAR path. + * @param jdbcDriverCls JDBC driver class. + * @param jdbcUrl JDBC URL. + * @param jdbcInfo Properties to connect to database. + * @return Connection to database. + * @throws SQLException + */ + private Connection connect(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl, + Properties jdbcInfo) throws SQLException { + if (AgentMetadataDemo.isTestDriveUrl(jdbcUrl)) + return AgentMetadataDemo.testDrive(); + + if (!new File(jdbcDriverJarPath).isAbsolute() && driversFolder != null) + jdbcDriverJarPath = new File(driversFolder, jdbcDriverJarPath).getPath(); + + return DbMetadataReader.getInstance().connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo); + } + + /** + * @param jdbcDriverJarPath JDBC driver JAR path. + * @param jdbcDriverCls JDBC driver class. + * @param jdbcUrl JDBC URL. + * @param jdbcInfo Properties to connect to database. + * @return Collection of schema names. + * @throws SQLException + */ + protected Collection<String> schemas(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl, + Properties jdbcInfo) throws SQLException { + if (log.isDebugEnabled()) + log.debug("Start collecting database schemas [drvJar=" + jdbcDriverJarPath + + ", drvCls=" + jdbcDriverCls + ", jdbcUrl=" + jdbcUrl + "]"); + + try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) { + Collection<String> schemas = DbMetadataReader.getInstance().schemas(conn); + + if (log.isDebugEnabled()) + log.debug("Finished collection of schemas [jdbcUrl=" + jdbcUrl + ", count=" + schemas.size() + "]"); + + return schemas; + } + catch (Throwable e) { + log.error("Failed to collect schemas", e); + + throw new SQLException("Failed to collect schemas", e); + } + } + + /** + * Listener for schema names. + * + * @return Collection of schema names. + */ + public Emitter.Listener schemasListener() { + return new AbstractHandler() { + @Override public Object execute(Map<String, Object> args) throws Exception { + String driverPath = null; + + if (args.containsKey("driverPath")) + driverPath = args.get("driverPath").toString(); + + if (!args.containsKey("driverClass")) + throw new IllegalArgumentException("Missing driverClass in arguments: " + args); + + String driverCls = args.get("driverClass").toString(); + + if (!args.containsKey("url")) + throw new IllegalArgumentException("Missing url in arguments: " + args); + + String url = args.get("url").toString(); + + if (!args.containsKey("info")) + throw new IllegalArgumentException("Missing info in arguments: " + args); + + Properties info = new Properties(); + + info.putAll((Map)args.get("info")); + + return schemas(driverPath, driverCls, url, info); + } + }; + } + + /** + * @param jdbcDriverJarPath JDBC driver JAR path. + * @param jdbcDriverCls JDBC driver class. + * @param jdbcUrl JDBC URL. + * @param jdbcInfo Properties to connect to database. + * @param schemas List of schema names to process. + * @param tblsOnly If {@code true} then only tables will be processed otherwise views also will be processed. + * @return Collection of tables. + */ + protected Collection<DbTable> metadata(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl, + Properties jdbcInfo, List<String> schemas, boolean tblsOnly) throws SQLException { + if (log.isDebugEnabled()) + log.debug("Start collecting database metadata [drvJar=" + jdbcDriverJarPath + + ", drvCls=" + jdbcDriverCls + ", jdbcUrl=" + jdbcUrl + "]"); + + try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) { + Collection<DbTable> metadata = DbMetadataReader.getInstance().metadata(conn, schemas, tblsOnly); + + if (log.isDebugEnabled()) + log.debug("Finished collection of metadata [jdbcUrl=" + jdbcUrl + ", count=" + metadata.size() + "]"); + + return metadata; + } + catch (Throwable e) { + log.error("Failed to collect metadata", e); + + throw new SQLException("Failed to collect metadata", e); + } + } + + /** + * Listener for tables. + * + * @return Collection of tables. + */ + public Emitter.Listener metadataListener() { + return new AbstractHandler() { + @SuppressWarnings("unchecked") + @Override public Object execute(Map<String, Object> args) throws Exception { + String driverPath = null; + + if (args.containsKey("driverPath")) + driverPath = args.get("driverPath").toString(); + + if (!args.containsKey("driverClass")) + throw new IllegalArgumentException("Missing driverClass in arguments: " + args); + + String driverCls = args.get("driverClass").toString(); + + if (!args.containsKey("url")) + throw new IllegalArgumentException("Missing url in arguments: " + args); + + String url = args.get("url").toString(); + + if (!args.containsKey("info")) + throw new IllegalArgumentException("Missing info in arguments: " + args); + + Properties info = new Properties(); + + info.putAll((Map)args.get("info")); + + if (!args.containsKey("schemas")) + throw new IllegalArgumentException("Missing schemas in arguments: " + args); + + List<String> schemas = (List<String>)args.get("schemas"); + + if (!args.containsKey("tablesOnly")) + throw new IllegalArgumentException("Missing tablesOnly in arguments: " + args); + + boolean tblsOnly = (boolean)args.get("tablesOnly"); + + return metadata(driverPath, driverCls, url, info, schemas, tblsOnly); + } + }; + } + + /** + * Listener for drivers. + * + * @return Drivers in drivers folder + * @see AgentConfiguration#driversFolder + */ + public Emitter.Listener availableDriversListener() { + return new AbstractHandler() { + @Override public Object execute(Map<String, Object> args) throws Exception { + if (driversFolder == null) { + log.info("JDBC drivers folder not specified, returning empty list"); + + return Collections.emptyList(); + } + + if (log.isDebugEnabled()) + log.debug("Collecting JDBC drivers in folder: " + driversFolder.getPath()); + + File[] list = driversFolder.listFiles(new FilenameFilter() { + @Override public boolean accept(File dir, String name) { + return name.endsWith(".jar"); + } + }); + + if (list == null) { + log.info("JDBC drivers folder has no files, returning empty list"); + + return Collections.emptyList(); + } + + List<JdbcDriver> res = new ArrayList<>(); + + for (File file : list) { + try { + boolean win = System.getProperty("os.name").contains("win"); + + URL url = new URL("jar", null, + "file:" + (win ? "/" : "") + file.getPath() + "!/META-INF/services/java.sql.Driver"); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) { + String jdbcDriverCls = reader.readLine(); + + res.add(new JdbcDriver(file.getName(), jdbcDriverCls)); + + if (log.isDebugEnabled()) + log.debug("Found: [driver=" + file + ", class=" + jdbcDriverCls + "]"); + } + } + catch (IOException e) { + res.add(new JdbcDriver(file.getName(), null)); + + log.info("Found: [driver=" + file + "]"); + log.info("Failed to detect driver class: " + e.getMessage()); + } + } + + return res; + } + }; + } + + /** + * Wrapper class for later to be transformed to JSON and send to Web Console. + */ + private static class JdbcDriver { + /** */ + public final String jdbcDriverJar; + /** */ + public final String jdbcDriverCls; + + /** + * @param jdbcDriverJar File name of driver jar file. + * @param jdbcDriverCls Optional JDBC driver class. + */ + public JdbcDriver(String jdbcDriverJar, String jdbcDriverCls) { + this.jdbcDriverJar = jdbcDriverJar; + this.jdbcDriverCls = jdbcDriverCls; + } + } +}
